0% found this document useful (0 votes)
11 views1,001 pages

Java Rules Vol.2 (2003)

Java™ Rules, Volume 2, authored by Douglas Dunn, focuses on mastering the fundamentals of the Java programming language, providing an extensive and detailed reference for readers. The book emphasizes a comprehensive understanding of language fundamentals while deliberately sacrificing brevity to include intricate details often overlooked in other texts. It aims to bridge the gap between beginner tutorials and advanced programming books, ensuring clarity and accessibility in its technical writing style.

Uploaded by

anilkuj
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views1,001 pages

Java Rules Vol.2 (2003)

Java™ Rules, Volume 2, authored by Douglas Dunn, focuses on mastering the fundamentals of the Java programming language, providing an extensive and detailed reference for readers. The book emphasizes a comprehensive understanding of language fundamentals while deliberately sacrificing brevity to include intricate details often overlooked in other texts. It aims to bridge the gap between beginner tutorials and advanced programming books, ensuring clarity and accessibility in its technical writing style.

Uploaded by

anilkuj
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1001

Java™ Rules, Volume 2

Mastering the Fundamentals


of the Java Programming Language
Java™ Rules, Volume 2
Mastering the Fundamentals
of the Java Programming Language

Doug Dunn
Java is a registered trademark of Sun Microsystems, Inc. Windows 95, Windows NT, Win-
dows 2000 and Windows XP are trademarks of Microsoft Corporation. All other product or
company names mentioned herein are the property of their respective owners. If the pub-
lisher was aware of a trademark claim, the product or company name is capitalized.
The publisher has taken care in the preparation of this book, but makes no expressed or
implied warranty of any kind and assumes no responsibility for errors or omissions. No lia-
bility is assumed for incidental or consequential damages in connection with or arising out
of the use of the information or programs contained herein.
The publisher is excepting back orders for printed editions of this book. For more informa-
tion, please write to [email protected].

Visit the author’s Web site at www.javarules.com.


Library of Congress Cataloging-in-Publication Data
Dunn, Douglas, 1958-
Mastering The Fundamentals of the Java Programming Language / Douglas Dunn.
p. cm.
Includes bibliographical references and index.
ISBN 0-201-70916-3
1. Java (Computer program language) I. Title.

QA76.73.J38 D84 2001


005.13’3—dc21
2001034111

Copyright © 2003 by Douglas Dunn


All rights reserved. No part of this publication may be reproduced, stored in a retrieval sys-
tem, or transmitted, in any form, or by any means, electronic, mechanical, photocopying,
recording, or otherwise, without the prior consent of the publisher. If you have downloaded
an electronic copy from the author’s Web site www.javarules.com, a single copy
may be printed for your personal use only.
0-201-70916-3
This book is dedicated to the child in each of us
who keeps asking the annoying question “Why?”

– Doug
Table of Contents
Be advised that this table of contents (TOC) only includes the first two section
levels (referred to as heads in the publishing industry). For a complete TOC, see
the chapter-level TOC at that beginning of each chapter. They include section
numbers with four components such as 1.5.2.1 The Problem of Changeable
Inlined Constants in the first chapter.

Table of Contents VII


Preface for Volume 2 XIII
About This Book XIX
A Java Tradition XXIX
A Pet Peeve XXXI
Vocabulary List 989
Index 997

Fields and Methods 33


1.1 Introduction 34
1.2 Fields 35
1.3 Field Initialization 38
1.3.1 Automatic Initialization Using Standard Default Values 38
1.3.2 Initialization Blocks 39
1.3.3 Constructors 42
1.4 Field Initialization Anomalies 55
1.4.1 The Problem of Forward Referencing 56
1.4.2 Invoking Overridden Methods During Object Initialization 61
1.4.3 Inlined Constants Always Appear to Have Been Initialized 65
1.4.4 The Variable Initializer for a Field is Always Executed 68
1.4.5 StackOverflowError During Object Initialization 70
1.4.6 Throwing Checked Exceptions During Initialization 73
1.5 Constants 81
1.5.1 Compile-Time Constant Expressions 82
1.5.2 Inlined Constants 89
1.5.3 Declaring Mutable Objects final 101
1.5.4 Blank Finals 102
1.5.5 Enumerated Types 106
1.5.6 Declaring Local Variables and Parameters final 126
1.5.7 The Constant Interface Antipattern 130

VII
1.6 Methods 131
1.6.1 abstract Methods 134
1.6.2 Result Types and the return Statement 139
1.6.3 Formal Parameter Lists 144
1.6.4 The throws Clause 165
1.7 Local Variables 174
1.8 “Write Once, Compile Anywhere” 179
1.8.1 Definite Assignment 181
1.8.2 Unreachable Statements 184
1.9 Qualifying Type versus Compile-Time Declaration 188
1.10 The Five General Forms 193
1.10.1 The Meaning of a Simple Field or Method Name 198
1.10.2 Method Invocation Chaining 204
1.10.3 Casting a Target Reference 206
1.10.4 Accessing static Members using a Primary
Expression 208
1.11 Method Signatures 209
1.11.1 The Compiler-Enforced Method Contract 215
1.11.2 Overloaded Methods 225
1.11.3 Overriding and Dynamic Method Lookup 233
1.12 Method Forwarding 246

Scope, Shadowing, and Qualified Access 247


2.1 Introduction 248
2.2 Namespaces 249
2.2.1 The Meaning of a Simple or Partially Qualified Name 252
2.2.2 Disambiguating Type Names 254
2.3 The Fundamentals of Lexical Scoping 258
2.3.1 The Mysterious Scope Hole 266
2.3.2 Compilation Unit Scope 268
2.3.3 Members Shadow Declarations in Enclosing Scopes 268
2.3.4 The Scope of Types in the Unnamed Package 271
2.3.5 Circular Dependencies in Type Declarations 276
2.4 Shadowing 278
2.5 Obscuring 281
2.6 Observable Compilation Units and Packages 284
2.7 Qualified Access 286
2.8 Access Control 290
2.8.1 The protected Access Modifier 301
2.8.2 Full Access to the Members of an Enclosing Class 312

VIII JAVA RULES


2.8.3 Members More Accessible Than Their Class Type 315
2.8.4 Accessing the Implementation of Same Class Objects 323
2.9 Encapsulation 324

Hiding and Inheritance 329


3.1 Introduction 329
3.2 Hiding 332
3.3 The Definition of Baseclass 340
3.4 The Definition of Related Classes 341
3.5 Generalization in Inheritance Hierarchies 342
3.6 Inheritance 344
3.6.1 Interface Inheritance 345
3.6.2 Implementation Inheritance 347
3.6.3 Inheriting Overloaded Methods 362
3.7 Do Interfaces Extend the Object Class? 362
3.8 Inheriting Members With The Same Name 367
3.8.1 Re-Inheritance 367
3.8.2 Ambiguous Names Related to Inheritance 368
3.8.3 Inheriting Methods With the Same Signature 370
3.9 Designing Extensible Classes 374
3.10 Capping a Class Hierarchy 387

Expressions, Statements, and Blocks 391


4.1 Introduction 392
4.2 Expressions 393
4.2.1 Primary Expressions 397
4.2.2 Expression Statements and Other Top-Level Expressions 402
4.3 Operator Expressions 405
4.3.1 Numeric Promotion 407
4.3.2 Operator Order of Precedence and Parenthesized
Expressions 415
4.3.3 The Associative Property of Operators 428
4.3.4 Nondestructive Operators 430
4.4 Exceptions are Precise 431
4.5 The 38 Unary, Binary, and Ternary Operators 435
4.5.1 Increment and Decrement Operators -- and ++ 436
4.5.2 Negation Operators -, ~, and ! 438
4.5.3 The Elementary School Operators 444
4.5.4 Remainder Operator % 446
4.5.5 Boolean Logical Operators && , ||, &, |, and ^ 450

IX
4.5.6 Bitwise Operators &, |, ^, >>, >>>, and << 454
4.5.7 Ternary Conditional Operator ?: 465
4.5.8 The Simple and Compound Assignment Operators 470
4.6 The instanceof Type Comparison Operator 473
4.7 A Bitwise Primer 475
4.7.1 Bits 477
4.7.2 Converting Nybbles to Hexadecimal Digits 492
4.7.3 General-Purpose Integer to String Conversion Methods 498
4.7.4 Unsigned Bytes 501
4.7.5 Some Miscellaneous Uses of the Bitwise Operators 523
4.8 Statements 526
4.8.1 Control-flow Statements 529
4.8.2 Labeled Statements 554
4.8.3 Control-transfer Statements (a.k.a. Abrupt Completion) 560
4.9 Blocks 566

Type Conversions and the Cast Operator 571


5.1 Introduction 572
5.2 The Type of a Variable or Expression versus the Class of
an Object 573
5.2.1 The Phrase “type of an object” is in Prevalent Use 576
5.2.2 The Term class type is Where Everything Goes Afoul 577
5.3 Java is a Strongly Typed Language 583
5.4 Substitution is a Higher Concept than Polymorphism 587
5.5 Forbidden Conversions 595
5.6 Permitted Conversions 597
5.6.1 Identity Conversions 600
5.6.2 Primitive Type Conversions 601
5.6.3 Reference Type Conversions 612
5.7 Conversion Contexts 623
5.7.1 Simple Assignment Conversion Context 627
5.7.2 Method Invocation Conversion Context 632
5.7.3 Method Return Conversion Context 636
5.7.4 The Cast Operator 638
5.7.5 The Implicit Cast in a Compound Assignment Operation 643
5.8 Overloaded Method Matching 644
5.8.1 Choosing The Most Specific Applicable Method 645
5.8.2 The Declaring Class of Applicable Methods 650
5.9 Value Set Conversions 656

X JAVA RULES
Assertions, Exceptions, and Logging 659
6.1 Introduction 660
6.2 Assertions 661
6.2.1 Preconditions, Postconditions, and Invariants 671
6.2.2 assert false and Logic Traps (or Control-flow
Invariants) 679
6.2.3 Catching CloneNotSupportedException 682
6.3 An Execution Stack Primer 685
6.4 The Exception Mechanism 690
6.5 The Throwable Class Hierarchy 698
6.5.1 General Exception Classes 723
6.5.2 Unspecified Runtime Exceptions and Errors 736
6.5.3 Asynchronous Exceptions 759
6.6 Throwable Objects 762
6.6.1 The Loggable Interface 767
6.7 The throw Statement 768
6.8 The try Statement 785
6.8.1 The catch Clause 791
6.8.2 The finally Clause 817
6.9 Exception Handling 859
6.9.1 Rethrowing the Same Exception 864
6.9.2 Exception Translation and Chaining 867
6.9.3 Squelching an Exception 874
6.9.4 Setting an Error Flag 878
6.9.5 Retry 879
6.9.6 Try an Alternative 881
6.10 Uncaught Exceptions 883
6.10.1 Top-level Exception Handlers 884
6.10.2 A Stack Trace Primer 922
6.11 Logging 936
6.11.1 The Logger Namespace 952
6.11.2 Logging Configuration 957
6.11.3 Logging Methods 975
6.11.4 The Cleaner Thread (A Shutdown Hook in
LogManager) 984

XI
XII JAVA RULES
Preface for Volume 2
This book has been under continuous development for many years now. I began
writing it shortly after the JDK 1.0.2 release. From the beginning of this project,
I felt the need for something new and different to bridge the chasm between the
tutorials (as well written as many of them are) and the “this is all the neat stuff I
know” books written by the best and brightest. Both genres are indispensable. In
between are reference works that claim to be both comprehensive and detailed.
That is the genre to which Mastering the Fundamentals belongs, but it is dif-
ferent from all of the others in that it focuses entirely on language fundamentals.
For example, there are no other books within the reference work genre of which
I am aware that completely ignore (perhaps to my peril) graphics and network
programming. These are specialized subjects I hope to cover in future volumes,
but not until there is a proven market for my style of technical writing. That style
can be summarized as “an uncompromising level of detail in a technical writing
style that is easy to read.”
Why is an “uncompromising level of detail” so important? There are a lot
more pages on language fundamentals in this book than in any other Java book
ever written (or that ever will be written I am sure), including even the specifica-
tions. Mastering the Fundamentals deliberately sacrifices brevity in order to
focus on details that are not found in most books on the subject. This is a very
different approach to technical writing as found in almost every other computer
language book on the market, one that assumes readers are willing to read
more so long as the material is relevant (no “fluff” which Merriam-Webster
defines as “something inconsequential”1) and easy to read. One reader said he
liked my work because it didn’t have any “speed bumps,” an analogy that I
greatly appreciated. A “speed bump” is a passage of technical writing that
requires you to stop reading and ponder its meaning or significance. I have expe-
rienced this in some very popular works. One paragraph in a best seller always
comes to mind in this context because I had to think about what it meant for the
better part of a half hour, and was still not sure I understood what the author was

1. Merriam-Webster's Collegiate Dictionary online (www.m-w.com/).

XIII
saying. I argue that so long as the material is of some practical importance
and easy to read, length must be allowed to suffer. At what price brevity? To
carefully explain something in detail requires more words. It is that sim-
ple. Nevertheless most programmers do not like to read a lot. This is precisely

The promise of “no greater level of detail” is a basic premise


of this work.

why an “uncompromising level of detail” is so important to my style of technical


writing. The recompense for having to read more is the knowledge that when
you are done reading a section that your understanding of the subject is
complete. In other words, there is an implied assurance that you do not have to
read any more on that particular subject. This means you should have no unan-
swered questions at the end of a section. That would suggest a lack of detail,
and “no greater level of detail” is a basic premise of this work.
Finally, if you want to read less, simply be selective in the sections that you
read. This is a reference work; it is not designed to be read from cover to cover.
I seriously do not think you should even attempt a cover-to-cover read. I have
only done so myself two or three times.
This brings up an important point: Mastering the Fundamentals is way too
detailed to use as a tutorial (or light introduction) to the subject, but as a refer-
ence work I assure you it was written with beginners in mind.
The remainder of this Preface discusses the target audiences. Mastering
the Fundamentals has one very large and one very small target audience. Both
are mainstream business application programmers. By that I mean working
(class) programmers, most of whom will be found in corporations outside of Sili-
con Valley.
The larger of the two target audiences is mainframe programmers, Visual
Basic programmers, and a host of other professional programmers who do not
have a C or C++ background and who are learning Java for the first
time. Professional programmers learning a new language need both the detail
and some consideration for the fact that a lot of this stuff is completely new to
them. If this describes you, then I challenge you to open the book to any page

XIV JAVA RULES


and start reading (preferably at the beginning of a section). If you feel completely
lost, put it back on the figurative bookshelf and move on because I have failed to
achieve my goal as a technical writer. If the text is easy to read, you fully under-
stand what is being said, and can appreciate the fact that it has not been
watered (or dumbed) down, then flip to another page and try the same thing
again. This is how the reference work genre should be judged —one section at a
time (how reference works are used).
The smaller target audience is the crème de la crème of a new generation of
computer programmers, the new talents, highly motivated professionals with
three years or less of experience and who are using Java on a daily basis. A
select few of you will not be satisfied with a working knowledge of the Java pro-

Programming at its best approaches art, the art of simplicity


expressed in logical constructs.

gramming language. Mastering the Java programming language will change not
only how you approach the work of computer programming, but the industry as
a whole. The obvious place to begin to excel is exactly the material in this book.
Thanks to Dr. Gosling and the other language designers at Sun, Java is a pro-
gramming language that can in fact be mastered in a couple years.
Nothing would bring me greater satisfaction than to learn that this work has
earned a place on the bookshelf of a significant number of teachers and univer-
sity professors. I have diligently sought to find the most natural organization of
the material, one that I could feel justified referring to as a reference work. This
has involved untold thousands of permutations over a period of more than seven
years and has yielded some interesting results (such as the sections on field ini-
tialization anomalies in the first chapter). I have also built a number of concep-
tual frameworks from scratch. More than mere exercises in vocabulary, these
conceptual frameworks are designed to help students understand language fun-
damentals at a professional level. Among them are constructor mathematics,
inlined constants, system-induced argument checks, the five general
forms (of field access and method invocation expressions), the compiler-
enforced method contract, compilation unit scope, type privileges, catch-

XV
all exception handlers, umbrella exceptions, and many others. I also tackle
some really tough subjects such as the practical uses of bitwise operators,
the type of a variable or expression versus the class of an object, and sub-
stitution as a higher concept than polymorphism that other books on the
subject tend to either skirt or completely ignore.
I owe it to myself to also mention that while selling this book online (before
Volume 1 was published by Addison Wesley Professional), I had many mechanical
engineers as well as computer programmers from the Indian subcontinent inter-
ested in my work. In both cases I felt honored, but the Indian readers were of par-
ticular interest to me. Finally, I asked one why he thought so many of his fellow
countrymen would be interested in my work. His reply was that my style of techni-
cal writing was more like that of the English than Americans. If this is so, I have no
idea why.
More generally, this book is for any programmer who wants to truly
master the fundamentals of the Java programming language. It is the travel
log I kept while making the same journey. While formalism and stylistic norms
forbid me from including students in the target audience, I firmly believe that this
book can be profitably used to teach the Java programming language in a class-
room setting (if not as a primary text, then as an auxiliary reference work).

Acknowledgements
I have had to wipe the slate clean in this department, so I will start with the latest
help that I got from Neal Gafter. Neal Gafter, I was surprised to learn, is the only
software engineer working on the javac compiler. That’s his baby. He recently
helped me to understand overloaded method matching and the changes that are
taking place in the 1.4.2 release (the declaring class of applicable methods is no
longer relevant). A warning here, however. Just because someone helped
doesn’t mean they approve of the resulting text. Gafter showed me a lot of
patience. On the other hand, if you learn something while reading 5.8 Over-
loaded Method Matching, please believe me when I say that it is entirely due to
the generosity of Neal Gafter. Thanks Neal. I hope you don’t regret it after read-
ing the final text.

XVI JAVA RULES


Thomas Paul is a “sheriff” at the Java Ranch. You can read his bio there.2 He
prides himself for having done more books reviews than any other sheriffs or
bartenders at the ranch. In fact, Tom is doing a full technical review of Master-
ing The Fundamentals. He is the first person to offer to do so since I reopened
the Web site. This is the lifeblood of a technical writer, and I am very grateful for
his help. More importantly, Tom and the rest of the folks at the ranch have been
extremely supportive of all my work. If you have not already visited the Java
Ranch, this “greenhorn” invites you to do so. The URL (a clickable link) is
www.javaranch.com.

2. See www.javaranch.com/contact.jsp#ThomasPaul.

XVII
XVIII JAVA RULES
About This Book

No Pagination
The term pagination refers to various techniques used to format the pages in a
printed book. In particular, it refers to page breaks and their impact on the for-
mat of the rest of a page. Electronic editions of Mastering The Fundamentals
are not paginated. Pagination is a huge job and it must be redone every time a
book is published. I will be posting new versions far too frequently to paginate
every time. This may result in some rather odd page breaks in printed copies of
the electronic book.

Cross-References are Clickable Links


In the electronic edition of Mastering the Fundamentals, cross-references are
clickable links. However, they are not underlined. The way to spot links in this
book is to look for section numbers. For example, click on 1.5.2 Inlined Con-
stants to read my definition of inlined constants in the first chapter. Page num-
bers are also clickable links. This is very useful because Acrobat Reader has
“Previous View” (a.k.a. Back) and “Next View” (a.k.a. Forward) buttons that work
just like those in a browser. This makes navigating the electronic edition much
easier than flipping through the pages in a printed book. Most if not all URL such
as www.javarules.com are also clickable links.
When navigating the book using clickable cross-references, keep in mind
that some sections have “notes” above the section name that you will not see at
first. (Look for a horizontal line immediately above the section name.) These
notes are “meta-data” about the section.

XIX
Married to the Java Specifications
This book is based on The Java Language Specification (JLS 1). In fact, I
started out to write what I regarded as The Java Language Specification for
Mainstream Business Application Programmers. Shortly thereafter, how-
ever, I realized that material from The Java Virtual Machine Specification
(JVMS) would have to be included as well. The work has grown to include every-
thing of interest to mainstream business application programmers in a host of
Java specifications, including notably the Second Editions of both the JLS and
the JVMS.
The main difference between the specifications and this book is the target
audience. The JVMS, of course, is written for someone who wants to implement
a JVM. What is not as generally understood is that the JLS is a grammar
intended for someone who wants to write a Java compiler, such as the jikes
compiler team at IBM. This is a very small group of people, undoubtedly well
known to each other. For example, the javac compiler is developed and main-
tained by one software engineer (Neal Gafter, a widely recognized authority on
the JLS). Because of this very small target audience, the JLS includes a lot of
material that is of interest only to those programmers who make a living in the
arcane world of compilers. That is truly unfortunate because the JLS contains a
wealth of information of interest to application programmers that rarely makes it
into mainstream Java books. The same can be said of the JVMS. This book
extracts all of that information, elaborates upon it where necessary, and pre-
sents it in a technical writing style appropriate for application programmers.
Much more is covered, but the reader is guaranteed that both the JLS and
JVMS have been diligently searched for every scrap of information that is
of interest to application programmers .

1. Technically speaking, the initialisms JLS and JVMS should be italicized because they are book
titles. My decision not to do so is based on font aesthetics and nothing else.

XX JAVA RULES
Section Names are Very Descriptive
I have over the years decomposed unwieldy sections into subsections, each of
which have very descriptive names. Because the section names are so
descriptive, the chapter-level TOC is intended to replace the usual intro-
duction to a chapter (which more often than not is simply an overview of the
chapter contents). Thus, I tend to use chapter introductions for miscellaneous
notes such as pointing out sections that I think are of particular importance,
explaining omissions, and the like. In short, the chapter introductions in this book
are little more than footnotes for the chapter-level TOC.

Correct Usage is Strongly Emphasized


Correct usage is strongly emphasized throughout this work. The first use of any
significant term appears in bold. A definition can be found in the surrounding
text. For example, the Vocabulary List in the back of this book is a complete
list of these words, comparable to the “term definition” entries in the index of
both the JLS and JVMS, only compiled as a separate list. I use this list to make
sure that terms are defined only once. The decision to call it a “Vocabulary List”
is merely an effort to increase awareness of these terms and how they are used.
This is part of a much larger effort that I refer to as correct usage.
Standardization is essential to that effort and the JLS, JVMS, and other
important Java specifications are that standard. When appropriate, nonstandard
(but widely used) Java terminology collected from dozens of books in and out of

In matters of terminology, the author always defers to the software


engineers and technical writers at Sun Microsystems.

print is also considered. Occasionally, I introduce a new term “of my own mak-
ing” (as I like to say) or take exception with the usage in official specifications.
However, this is never done without first alerting the reader.
Correct usage is nothing to brag about. More experienced programmers may
actually look unfavorably at technical writers who place an unusually strong empha-

XXI
sis on using terms correctly. There are more important things to think about. Who
really cares if a term is used loosely? We are computer programmers, not doc-
tors or lawyers. I agree. As your knowledge of computer programming increases,
the importance of terminology diminishes. All that really matters is good inter-
face design and a bug-free, efficient implementation.
For less experienced programmers, however, meaning is attached to terms.
If terms are used consistently and have a clear meaning, the learning process
can be reduced to the acquisition of new terms, the building of a vocabulary that
eventually translates into the actual writing of programs. For programmers at
this stage of development (which includes all of my target audience) terms
are the building blocks of knowledge. It is unnatural for a technical writer not
to respect this reality, unless perhaps the target audience is more experienced
programmers. Then correct usage is not as important.
So please, if at times I seem overbearing or a zealot when it comes to mat-
ters of terminology, try to take it in your stride. I am not trying to change the
culture of computer programming; I am just trying to do my job as a tech-
nical writer.

Release Information
I recall Dr. Gosling once saying that he spends an inordinate amount of time behind
closed doors speaking to lawyers. I have never fully understood why “Java 2 Plat-
form” was added to the beginning of official release names starting with the 1.2
release, but suspect that it is somehow related to those closed door sessions. Like
most software engineers and technical writers, I use what Joshua Bloch refers to
as engineer version numbers2 (a.k.a. platform versions) such as “the 1.4
release” instead of official release names such as “Java 2 Platform, Standard
Edition v 1.4” (or J2SE v1.2 for short). There is no significant difference (for pro-
grammers at least) because the engineering version number is part of the official
release name. Figure 1.1 shows how to read an engineering version number.

2. Joshua Bloch, Effective Java Programming Language Guide, (Boston: Addison-Wesley,


2001), Chapter 1, “Introduction.”

XXII JAVA RULES


Figure 1.1 Release numbers

The java -version DOS command can be used to determine which ver-
sion of Java you are running. Note that the -version option is the only way to
determine what specific build you are using, such as 1.4.1_01-b01.
There never has been a new major release; 2.0 will be the first. Starting
with the 1.2 release, Sun committed to a new functionality release approxi-
mately once every 18 months. (These are sometimes referred to as feature
releases.) As the name implies, functionality releases introduce new packages
as well as changes (new classes, methods, etc.) to existing packages. Also
beginning with the 1.2 release, there are at most one or two maintenance
releases for each functionality release. At least some of them have insect code
names such as (praying) Mantis because they are primarily bug fixes. This also
explains why they are sometimes referred to as bug releases.
Beginning with the 1.1.4 release, Sun livened things up a bit by using code
names such as Merlin for the 1.4 release and Tiger for the 1.5 release. Table
1.1 includes all of the code names of which I am aware. Many programmers who
research the Bug Database on a regular basis find these code names mind
numbing, especially in light of the fact that Sun has never bothered to publish
something akin to Table 1.1, which would make it possible to translate code
names into engineering release numbers.

XXIII
Table 1.1 Release Information
Latest
Engineering JDK/Java 2 Update
Version SDK Code Name Release Dates Releasea

1.0.2 JDK

1.1 JDK

1.1.1 JDK

1.1.2 JDK

1.1.3 JDK

1.1.4 JDK Sparkler

1.1.5 JDK Pumpkin

1.1.6 JDK Abigail _009

1.1.7 JDK Brutus _007

1.1.8 JDK Chelsea _010

1.2 Java 2 SDK Playground late 1998

1.2.1 Java 2 SDK _004

1.2.2 Java 2 SDK Cricket _13

1.3 Java 2 SDK Kestrel _05

1.3.1 Java 2 SDK LadyBird _05

1.4 Java 2 SDK Merlin _02

1.4.1 Java 2 SDK Hopper _01

1.4.2 Java 2 SDK Mantis early 2003

1.5 Java 2 SDK Tiger mid to late 2003


a. These are the latest update (or micro) version numbers for the Windows platform. The latest update version
for other platforms may be different. Note also that beginning in the 1.2.2 release, update (or micro) release
numbers switched from three to two digits.

All of my efforts to obtain a list of official release dates were in vain.


Apparently, there is no such list. It simply doesn’t exist anywhere at Sun. I
have left the “Release Dates” column in this table largely empty in protest.
Another column in this table is “JDK/Java 2 SDK.” At the same time that Sun
started to use the term Java 2, the name of the Java Development Kit (JDK)
was changed to the Software Development Kit (SDK).

Use of The javap Class File Disassembler


A number of class files are decompiled using javap (the class file disassem-
bler), which is one of the basic tools in the SDK. (Despite the name of this tool, I
refer to javap output as decompiled rather than disassembled.) This tool
makes it possible to read the contents of a class file. It is very easy to use. In
fact, I use the exact same javap -c Test DOS command every time (Test
being the name of the class file). While I would encourage everyone to use the
class file disassembler as a tool for studying the language, the output can be
rather intimidating for the uninitiated. I carefully explain such examples. There is
no expectation whatsoever that the reader understand decompiled code.

An Apology for the Windows Bias


Most technical writers, however hard they try not to, evidence either a Unix or
Windows bias in their text. To some extent, this is unavoidable. Perhaps the
worst offence in my books is DOS commands such as the following:

C:\Java\classes>java Test

In order to make the DOS command appear more natural (at least to Window’s
users), I always use C:\Java\classes> as the DOS prompt. Such a default
DOS prompt indicates that C:\Java\classes is the current working direc-
tory. Otherwise, I have tried my best to be a platform-neutral author, but the
effort is imperfect, and I apologize in advance to any hard-core UNIX or Mac pro-
grammers who may take offense.

XXV
Examples of Code
Most of the examples in this book are so short and easy to read that to explain
them might insult some readers. This is very deliberate. I personally do not like
the long examples found in most other Java books. Code is difficult to read. You
have to acquaint yourself with variable names and other intricacies of the code
that have no long-term benefit to the reader. In this book, examples are always
as short as I can make them, preferably no more than 10 to 15 lines of code.
Furthermore, bold is often used to help to draw the eye to the most import lines
of code. This makes it possible to quickly read and comprehend an example
without a detailed, blow-by-blow description of what the code is doing.
Another thing that annoys me about the examples in other Java books is the
lack of output. Not only are you expected to read and understand lengthy exam-
ples, but there is an assumption that you already know what the code outputs. I
always show the output from examples.
There are two kinds of examples: the most common is a short program
named Test; the other is based partly or wholly on snippets of code from the
core API of a J2SE release. These snippets usually consist of no more than a
few lines of code. Nevertheless, one must respect the copyright notices at the
top of each of the compilation units in the core API. Therefore, the examples of
source code from the core API are never the actual source code. They are sim-
plified versions of the same code that are good only for instructional purposes.
That is why I always qualify the introduction of these examples by saying that the
source code “looks like this.” That is, it is not the actual source code, but is
close enough to make the salient point without either breaking copyright laws or
bringing any disrepute to the software engineers at Sun. If I feel that a particular
example is overly simplified, I will usually include a note explicitly reminding the
reader that the actual source code is different.

The Inclusion of Bug Ids


I include many Bug Ids from the Bug Parade on the Java Developer Connection
(JDC) Web site. The URL for looking up those bugs is

XXVI JAVA RULES


developer.java.sun.com/developer/bugParade/
index.jshtml
You need to be a JDC member to access this Web address. Membership is free
and highly recommended. Bug Ids are usually found solely in footnotes, but
some bugs have aged so that they become de facto, undocumented features of
the Java programming language. None comes more readily to mind in this con-
text than Bug Id 4057172, dated June 6, 1997. This bug explains why javac
and some other Java compilers tolerate extraneous semicolons. Such “bugs”
are discussed in the main body of the text rather than in footnotes.

The Use of Formal Parameter Lists in Method Names


When referring to a method in the core API, I use the method name followed by
the formal parameter list exactly as it appears in the API docs of the J2SE. This
includes notably the parameter names (which are often changed in other Java
books). This makes it easy to switch back and forth between my work and the
API docs.
An exception is made for overloaded methods, however. They are referred
to solely by their method name, such as the fully overloaded print and
println methods. Occasionally, I make other exceptions. For example, I
always refer to the main(String[] args) method as just main, and a
handful of methods in the core API have formal parameter lists so long as to
defy their use. None comes more readily to mind in this context than
System.arraycopy(Object src, int src_position, Object
dst, int dst_position, int length), which I always refer to as
just System.arraycopy.
Instance methods are never qualified by the name of the class in which they
appear. For example, I would never refer to “the String.length() method”
because length() is an instance method. Instead, this book refers to such
instance methods as “the length() method in String.” This practice is
contrary to almost every other Java book in print. The rationale for this decision
is simply that String.length() would never appear in code and is there-

XXVII
fore (at least to my way of thinking) potentially misleading. Only class methods
such as System.arraycopy are qualified with the name of the class in which
they are declared (as they also appear in code).

Compiler Error Messages


This book makes extensive use of compiler error messages from the javac
compiler. Compiler error messages are always improving at Sun, which is why I
have recompiled most, if not all, of the examples in this book using a 1.4.1 com-
piler immediately prior to publication. If you use a different compiler or a differ-
ent version of the same compiler, the error messages may be different. The JLS
does not specify compiler error messages, only compiler errors.

Deprecated Members
My general policy towards deprecated classes, fields, methods, and construc-
tors is to pretend as if they do not exist. Deprecated code is something that
should ideally be removed from the language. Why waste time explaining the way
things used to be? The only exception to this rule is when explaining why some-
thing was deprecated helps to better understand the current API.

XXVIII JAVA RULES


A Java Tradition
There is a tradition in Java technical writing of quoting great thinkers that started
with the First Edition of The Java Language Specification. While there are
many great thinkers I would like to quote, the following passage from Walden
most often comes to mind when I think back on some of my experiences in com-
puter programming.
The laboring man has not leisure for a true integrity day by day. He can-
not afford to sustain the manliest relations to men. His labor would be
depreciated in the market. He has no time to be anything but a
machine. How can he remember well his ignorance which his growth
requires who has so often to use his knowledge. …The finest qualities
of our natures, like the bloom on fruits, can be preserved only by the
most delicate handling. Yet we do not treat ourselves nor one another
thus tenderly. The mass of men lead lives of quiet desperation.

– Henry David Thoreau


Walden or, Life in the Woods
My motivation in writing this book has been to be of service to others. It has
been a labor of love, my effort to do some good in society by benefiting “the
mass of programmers.”
We are all in this together. The body of knowledge we strive to master is so
vast that I have found it to be a verity that truly great programmers are hum-
ble people. Dr. James Gosling, the inventor of Java technology, is a perfect role
model in this regard.

XXIX
XXX JAVA RULES
A Pet Peeve
In a Bill Venner interview, Dr. Gosling was asked some questions about Design
by Contract. The following is excerpted from that interview.
I believe it is completely beyond the state of the art to try to do that kind of
analysis statically, that particular one, but there are all kind of analyses that
you could actually do. You don't declare failure if you can't do everything stati-
cally, but every thing that you can push into the static analysis phase of
the system to get earlier and earlier is yet another source of reliability
to the system.1 [emphasis added]

This emphasis on “static analysis” is at odds with the following evaluation of Bug
Id 4128179, “Add a lint-like facility to javac.”
Many RFE's have been submitted against javac requesting warnings of the sort
that lint would give for C. Because of the importance of comformance [sic] and
the role that javac plays in establishing it, it is our policy … to not give
warnings by default. However, an optional mode that is not the default would
be fine.2

What we are talking about here is compiler warnings, or rather the lack of them.
A chapter should be added to the JLS so that “Write Once, Compile Anywhere”
(which at present describes only definite assignment and unreachable state-
ments) is extended to include compiler warnings. Special flags (also known as
compiler options) such as -Xswitchcheck should not be used to solve this
problem, —not on the Java platform. Every compiler will have different options,
and Java programmers will not benefit from the standardization to which they
have become accustomed. In short, all of the software engineers responsible for
writing Java compilers should sit down at the earliest possible date and have a
compiler warnings powwow.

1. Dr. James Gosling in an interview with Bill Venner entitled “A Conversation with James Gosling
(May 2001),” on the artima.com Web site (Artima Software, Inc.), www.artima.com/intv/
gosling310.html. Please remember that Dr. Gosling is speaking extemporaneously. There also
appears to be some transcription errors that I have not marked “[sic]” or bothered to correct.
2. Evaluation of Bug Id 4128179.

XXXI
XXXII JAVA RULES
Chapter 1

Fields and Methods

Chapter Contents
1.1 Introduction 34
1.2 Fields 35
1.3 Field Initialization 38
1.3.1 Automatic Initialization Using Standard Default Values 38
1.3.2 Initialization Blocks 39
1.3.3 Constructors 42
1.3.3.1 Reference Constructors 48
1.3.3.2 Alternative Constructor Designs 50
1.4 Field Initialization Anomalies 55
1.4.1 The Problem of Forward Referencing 56
1.4.2 Invoking Overridden Methods During Object Initialization 61
1.4.3 Inlined Constants Always Appear to Have Been Initialized 65
1.4.4 The Variable Initializer for a Field is Always Executed 68
1.4.5 StackOverflowError During Object Initialization 70
1.4.6 Throwing Checked Exceptions During Initialization 73
1.5 Constants 81
1.5.1 Compile-Time Constant Expressions 82
1.5.2 Inlined Constants 89
1.5.2.1 The Problem of Changeable Inlined Constants 95
1.5.3 Declaring Mutable Objects final 101
1.5.4 Blank Finals 102
1.5.5 Enumerated Types 106
1.5.6 Declaring Local Variables and Parameters final 126
1.5.7 The Constant Interface Antipattern 130
1.6 Methods 131
1.6.1 abstract Methods 134
1.6.2 Result Types and the return Statement 139
1.6.2.1 Using Return Values to Indicate Failure 142
1.6.3 Formal Parameter Lists 144

33
1.6.3.1 Argument Checks 147
1.6.3.2 On const References 155
1.6.3.3 Making Defensive Copies 160
1.6.4 The throws Clause 165
1.7 Local Variables 174
1.8 “Write Once, Compile Anywhere” 179
1.8.1 Definite Assignment 181
1.8.2 Unreachable Statements 184
1.9 Qualifying Type versus Compile-Time Declaration 188
1.10 The Five General Forms 193
1.10.1 The Meaning of a Simple Field or Method Name 198
1.10.2 Method Invocation Chaining 204
1.10.3 Casting a Target Reference 206
1.10.4 Accessing static Members using a Primary Expression 208
1.11 Method Signatures 209
1.11.1 The Compiler-Enforced Method Contract 215
1.11.1.1 throws Clause Conflicts in abstract Methods 221
1.11.1.2 Covariant Result Types 224
1.11.2 Overloaded Methods 225
1.11.3 Overriding and Dynamic Method Lookup 233
1.11.3.1 The invokeinterface machine instruction 241
1.12 Method Forwarding 246

1.1 Introduction
Methods and constructors have a lot in common. According to the JLS, the fol-
lowing are “identical in structure and behavior”1 for both constructors and meth-
ods.
• Parameter lists
• Signatures
• The throws clause
• Overloading2
Because of these similarities between methods and constructors, many of the
sections on methods in this chapter apply equally to constructors.

1. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java Language Specification,
Second Edition (Boston: Addison-Wesley, 2000), §8.8.1, “Formal Parameters,” §8.8.4, “Construc-
tor Throws,” and James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First
Edition, (Reading: Addison-Wesley, 1996), §8.6.2, “Constructor Signature.” (Do not update.)

34 JAVA RULES
I consider 1.10 The Five General Forms (of field access and method invoca-
tion expressions) to be one of the most important sections in the whole book.
There are a great many sections throughout this volume and the first that make
reference to at least one of the five general forms. The term five general forms
is mine. However, it is merely a restatement of the JLS rules for determining the
class or interface searched by a compiler to find the declaration of a field or
method —one that is better adapted for classroom settings.
This chapter makes extensive use of the <init> and <clinit> method
names. The more formal names of these methods are instance initialization
method and class initialization method, both of which are simply too long for
repeated use. If you are not familiar with the <init> and <clinit> methods,
they are discussed in 2.3.1 Special Initialization Methods in Volume 1.
The only field and method modifiers discussed in detail in this chapter are
final and abstract. The access modifiers public, protected, and
private are discussed in the next chapter. The static and strictfp
modifiers are discussed in Volume 1. The other field and method modifiers
(transient, volatile, synchronized, and native) are discussed in
an as yet unpublished volume.

1.2 Fields
Fields are either class variables, instance variables, or interface constants. They
are declared in a class or interface body, whereas local variables are declared in
a block. Fields and local variables share a common declaration syntax, which is
discussed in Table 1.1. The constants declared in the body of an interface are
implicitly public static final. These are the only modifiers that can be
used when declaring interface constants. The explicit use of constant modifiers
was actively discouraged “as a matter of style” in the original JLS.

2. Gosling et al., The Java Language Specification, §8.8.6, “Constructor Overloading.” Actually
the JLS says only that constructor overloading “is identical in behavior,” not “identical in structure
and behavior.” Constructors are inherently overloaded because they have the same name as the
class in which they are declared, which (I presume) explains why constructor overloading is not also
“identical in structure.”

FIELDS AND METHODS 35


Table 1.1 The Three Parts of a Field or Local Variable Declaration
Part Notes

b Field or local variable The field modifiers for class and instance variables
modifiers are the access modifiers (public, protected,
and private ), static, final,
transient, and volatile. The only field
modifiers for interface constants are public,
static, and final. These are referred to as
interface constant modifiers. The only local
variable modifier is final.

c Type name The type of the field or local variable.

d Variable Identifier The name of the field or local variable.


declarator
Variable A variable initializer consists of the simple assignment
initializer operator followed by an expression that must
evaluate to a type that is assignment compatible with
the type name used in the field or local variable
declaration.

Every field declaration in the body of an interface is implicitly public,


static, and final. It is permitted, but strongly discouraged as a
matter of style, to redundantly specify any or all of these modifiers for
such fields. 3 [emphasis added]

The emphasized text (“but strongly discouraged as a matter of style”), however,


was removed the Second Edition of the JLS. One assumes that the use of con-
stant modifiers in interface types is now considered an acceptable style of cod-
ing. I would add that, if you are going to use them, you should probably use all
three.
When more than one field modifier is used in a declaration, “it is customary,
though not required”4 to list them in the following order:

[public protected private] static final transient volatile

3. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
§9.3, “Field (Constant) Declarations.” (Do not update.)
4. Gosling et al., The Java Language Specification, §8.3.1, “Field Modifiers.”

36 JAVA RULES
This order is not arbitrary. It reflects the order in which the modifiers are
most commonly used, starting with the access modifiers and ending with
volatile. The same order of the access modifier followed by static and
then final is also used in method declarations.
There is only one illegal combination of field modifiers. A field cannot be
declared both final and volatile. To do so would be an inherent contra-
diction because final fields are constants and the volatile modifier
implies change.
The default serialization mechanism uses the transient keyword to
determine which instance variables to include in the serialized form. Instance
variables that are not declared transient are said to represent the persis-
tent state of an object.
It is interesting to note that in alpha releases of the Java programming lan-
guage the name of the volatile modifier was threadsafe. The effect of
declaring a field volatile is that it can be safely accessed by unsynchronized
methods.
A variable declarator consists of an identifier and an optional variable ini-
tializer. There can be any number of comma separated variable declarators in
the same declaration statement. The purpose of a variable declarator is to allow
for more than one variable to be declared at a time. The modifier(s) and type
name are the same for all of the fields or local variables declared. For example,

/** Constant for the Unicode character block of the same name. */
public static final UnicodeBlock
BASIC_LATIN = new UnicodeBlock("BASIC_LATIN"),
LATIN_1_SUPPLEMENT = new UnicodeBlock("LATIN_1_SUPPLEMENT"),
LATIN_EXTENDED_A = new UnicodeBlock("LATIN_EXTENDED_A"),
LATIN_EXTENDED_B = new UnicodeBlock("LATIN_EXTENDED_B"),

This partial example of an enumerated type is from the Character.Uni-


codeBlock class. Some programmers think that each field or local variable
should be declared on a separate line. They therefore shun the use of variable
declarators. While this is understandably a matter of style, I would point out that
variable declarators are particularly useful in the declaration of enumerated

FIELDS AND METHODS 37


types. There are doubtless other times when the use of declarators makes a lot
of sense.
The memory for class variables and interface constants is allocated when
the class or interface is loaded. They are destroyed when the class or interface
is unloaded. Memory is allocated for instance variables when a class instance
creation expression is evaluated. They are destroyed when the when the object
is garbage collected.

1.3 Field Initialization


Fields are initialized as follows.
1. Fields are automatically initialized using standard default values immediately
after memory allocation
2. Either the <clinit> or <init> special initialization method is then exe-
cuted.
• Variable initializers and initialization blocks are executed in
textual order
• One or more constructors is executed
Field initialization is discussed further in the following subsections.

1.3.1 Automatic Initialization Using Standard Default Values


Unlike local variables that must be explicitly initialized by a programmer, fields
and array components are automatically initialized to one of the standard
default values in Table 1.2. Automatic initialization using standard default val-

Table 1.2 Standard Default Values


Data Type Default Initialization Literal Value

byte zero (byte)0


short zero (short)0
char NULL character '\u0000'
int zero 0
long zero 0L

38 JAVA RULES
Table 1.2 Standard Default Values
Data Type Default Initialization Literal Value

float positive zero 0.0f


double positive zero 0.0

boolean false false


All reference types null reference null

ues occurs immediately after memory is allocated for a field and before the
<init> or <clinit> method is invoked.
The question arises: Should fields be explicitly initialized with a stan-
dard default value? Doing so would seem redundant, but there is also a main-
tenance issue here. Looking only at the following declaration, can you tell if the
intent is to initialize count with the standard default value of zero or is field ini-
tialization being deferred?

int count;

I prefer to explicitly assign the standard default value. Doing so is self-document-


ing, making it clear that field initialization is not being deferred. For example,

int count = 0;

There is a slight performance price to pay, however, because the variable initial-
izer must be executed after default initialization has already occurred, in effect
assigning zero to a field that is already equal to zero. See 1.4.4 The Variable Ini-
tializer for a Field is Always Executed for a discussion.
Array components are fields in the dynamically created array classes. They
are always initialized to standard default values. It does not matter if the
array is created in a method or constructor and assigned to a local variable. In
that case, the array variable is local, not the array components.

1.3.2 Initialization Blocks


Initialization blocks are easy to recognize because they are freestanding blocks
of code that have at most one modifier (the static keyword) and no name. As

FIELDS AND METHODS 39


explained in 2.2 The Terminology of Class Body Declarations in Volume 1, the
JLS (and most other Java books) refer to these blocks of code as instance ini-
tializers and static initializers. I just think those terms are too easily con-
fused with instance variable initializer and class variable initializer
because all four terms end with initializer. That is why I use the terms instance
initialization block and static initialization block throughout Java Rules.
A static initialization block is executed only once, when the class is
loaded. Instance initialization blocks are executed every time a class is instanti-
ated. Initialization blocks do the work that variable initializers cannot.
There are three reasons for using initialization blocks:
• Complex initialization logic such as loading an array
• Invoking methods that throw checked exceptions (which generates a com-
piler error in variable initializers except in the case of anonymous classes)
• To serve as a replacement for constructors in anonymous classes (instance
initialization blocks only)
There is some overlap here because exception handling could be rightly
regarded as complex initialization logic. The remainder of this section discusses
each of these uses in order followed by a brief discussion of the specification
that initialization blocks must be able to complete normally.
Here is an example of using a static initialization block to load an array
(from the BitPattern utility class in 1.11.2 Overloaded Methods):

static final long[] BIT_MASK = new long[64];


static {
for (int i = 0, length = BIT_MASK.length; i < length; i++)
BIT_MASK[i] = 1L << i;
}

Loading this array requires several lines of code, which is simply more than can
fit into a variable initializer. Notice the placement of the curly braces. This style
of coding static initialization blocks is comparable to method declarations. It
minimizes the indentation of code in the block and also helps to standardize the
look and feel of source code.
With the exception of anonymous classes, variable initializers cannot invoke
methods that throw a checked exception. Therefore such methods must be

40 JAVA RULES
invoked in initialization blocks. In the case of static initialization blocks, the
checked exception must be caught and handled. Instance initialization blocks
can throw checked exceptions, but only if the checked exception is included in
the throws clause of every constructor in the class. For example,

class Test {
int instanceVariable = initialize();
Test() throws Exception { }
int initialize() throws Exception {
return 0;
}
}

For a complete discussion of this subject see 1.4.6 Throwing Checked Excep-
tions During Initialization.
Instance initialization blocks are rarely used as a replacement for construc-
tors in anonymous classes, which is ironic because that is precisely why they
were introduced to the language in the 1.1 release as part of the Inner Classes
Specification. (Anonymous classes cannot declare constructors because there
is no class name to use in the constructor declaration.) That part of the specifi-
cation was not implemented until the 1.4 release, however. By then the decision
had been made to allow the instance variable initializers in an anonymous class
to throw checked exceptions also. Invoking instance methods that throw
checked exceptions is one of the main reasons why a replacement for construc-
tors was needed in the first place.
Initialization blocks must be able to complete normally. This was a change to
the language that was not implemented until the 1.3 release. The primary Bug Id
is 4064281. It relates to the specification for unreachable statements, the very
first rule of which is that “the block that is the body of a constructor, method,
instance initializer or static initializer is reachable.”5 Remember that initialization
blocks are executed in the <clint> and <init> special initialization methods
in the same textual order in which they are found in the source code. If one of
them cannot complete normally then others that follow would not be reachable.
For example,

5. Gosling et al., The Java Language Specification, §14.20, “Unreachable Statements.”

FIELDS AND METHODS 41


public class Test {
static final int x;
static { while (true) {} }
static { x = 1; }
}

Attempting to compile this class generates the following compiler error:

Test.java:3: Initializer must be able to complete normally.


static { while (true) {} }
^
1 error

Exactly the same error message is generated if the static keyword is


removed from the front of these three statements.

NOTE 1.1
Constructors serve two purposes. One is the chaining together of in-
stance initialization methods, which is discussed in 2.4.1.2 Default
Constructors in the Second Edition of Volume 1. The other is dynamic
initialization of instance variables, which is the subject of the following
section.

1.3.3 Constructors
The term constructor is something of a misnomer. Constructors do not actually
“construct” anything. Leaving aside the issue of implicit or explicit superclass
constructor invocations (the chaining together of instance initialization methods),
constructors more or less just assign values to instance variables after an object
has been created. With or without a constructor, the runtime system creates and
initializes an object every time a class instance creation expression is evaluated.
That the this keyword can be used in constructors shows that the object is
created before the constructor (or rather, the corresponding <init> method)
is executed. The fact is that <init> methods are not any different from other

42 JAVA RULES
instance methods except that they happen to be the first method invoked after
an object is created.
The four ways in which to initialize instance variables are summarized in
Table 1-3. What makes constructors fundamentally different from other initializa-

Table 1.3 Instance Variable Initialization


Statically Dynamically
Instance Initialization Code Compiled Assigned

Automatic initialization using


standard default values

Variable initializers ✔
Instance initialization blocks ✔
Constructors ✔

tion code (automatic initialization using standard default values, instance variable
initializers, and instance initialization blocks) is that the values assigned to
instance variables can be different from one object to the next because they are
passed by client programmers as arguments in class instance creation expres-
sions. In the context of a discussion of constructor design, I refer to automatic

The initial value of every instance variable is either statically com-


piled or dynamically assigned in the body of a constructor.

initialization using standard default values, instance variables initializers, and


instance initialization blocks as statically compiled initial values. The argu-
ments passed to constructors are dynamically assigned initial values. The
critical difference is that statically compiled initial values are more or less the
same for all instances, whereas dynamically assigned initial values are poten-
tially different for each object created. I say “more or less” the same because
statically compiled values are not necessarily exactly the same for all instances.
For example,

FIELDS AND METHODS 43


Date today = new Date();

The value of today will be different for every object created. Nevertheless, they
are the same in the sense that they are the current date. Client programmers
could pass any date, including one in the past.
The term dynamically assigned initial values is just a fancy way of refer-
ring to constructor parameters, which are either required or optional. They are
required if every constructor in the class includes a parameter for a particular
dynamically assigned initial value. Otherwise, they are optional. For example,

public BeanDescriptor(Class beanClass)


public BeanDescriptor(Class beanClass, Class customizerClass)

These are the only two constructors in the BeanDescriptor class. A


BeanDescriptor cannot be created without passing the class of a bean;
beanClass is therefore a required constructor parameter whereas
customizerClass is an optional constructor parameter. Note that some
required constructor parameters may be used in required computations rather
than being directly assigned to instance variables.
Optional constructor parameters default if not specified. Default values are
by definition statically compiled, even if they are assigned in a constructor. For
example,

//Store as long because Date is mutable


private long date = new Date().getTime();
Widget() { }
Widget(Date date) {
if (date == null)
throw new NullPointerException();
this.date = date.getTime();
}

The date is an optional constructor parameter that defaults if not specified. In


this example, the default value is clearly a statically compiled initial value. The
following is an alternative implementation of the same constructor design.

//Store as long because Date is mutable


private long date;
Widget() {

44 JAVA RULES
date = new Date().getTime();
}
Widget(Date date) {
if (date == null)
throw new NullPointerException();
this.date = date.getTime();
}

Though assigned in a constructor, this default value is also statically compiled


because it is not passed as an argument.
Assigning the default value in a no-argument constructor is probably a better
implementation because when the Widget(Date date) constructor is
invoked, the new Date().getTime() variable initializer is a waste of time.
On the other hand, if there were more than one constructor that used the current
date as a default, invoking new Date() in the variable initializer requires less
code than assigning the same default value in each of the constructors. These
are the kind of things you have to think about when designing constructors.
To summarize, the initial value of an instance variable is always one of the
following.
1. Statically compiled
2. A required constructor parameter
3. An optional constructor parameter that defaults if not specified
These three possibilities form the basis of what I like to call constructor math-
ematics.6 If you consider the different combinations of these three kinds of ini-
tial values, every class can be described by one of the rows in Table 1.4. This is
not an exact science, however. Multiple constructor designs are as varied as are
the programming problems they solve. Two special considerations for any con-
structor design are
• There may be a choice of which superclass constructor to invoke. More-
over, the selection may depend on the arguments passed to a subclass con-

6. This term is derived from “constructor madness” as used in “Java Tip 63” in Java World, which
is entitled “Avoid ‘constructor madness.’” See www.javaworld.com/javaworld/javatips/
jw-javatip63.html.

FIELDS AND METHODS 45


structor. In such a case, deciding which superclass constructor to invoke is
an important part of the overall constructor design.
• Constructors should invoke other constructors in the same class whenever
possible to avoid redundant coding, especially when there are argument
checks and initialization logic involved (beyond the simple assignment
statements found in many constructors). These explicit constructor invoca-
tions are discussed in the following subsection on reference construc-
tors.
These additional considerations (and others) aside, it is helpful to have a
starting point to begin constructor design. That starting point is to list all of
the instance variables in the class, marking each as either statically compiled or
dynamically assigned. Then determine which of the dynamically assigned values
are required constructor parameters. Finally, you must decide which combina-
tions of optional constructor parameters make the most sense. Here it is impor-
tant to keep the number of constructors to a minimum. Too many constructors
becomes an interface design problem.

Table 1.4 Constructor Mathematics


Initial Values Constructor Requirements

B Either there are no instance variables None (uses the default constructor)
(such as a utility class) or all of the
instances variables have statically
compiled values.

C All instance variables are initialized with One constructor (which is alternatively
required constructor parameters. referred to as the minimal constructor)a

46 JAVA RULES
Table 1.4 Constructor Mathematics
Initial Values Constructor Requirements

D All instance variables default if not For every “mathematically possible” com-
specified. Therefore all of the bination of constructor parameters, there is
constructor parameters are optional. one constructor. Quotation marks are used
around “mathematically possible” because
very often not every combination makes
sense from an interface design perspective.
Assuming that all of the combinations are
used, however, the arithmetic is simple. You
use the number of optional constructor
parameters as a power of two to determine
the number of required constructors. For
example, if there are three optional
constructor parameters, the class would
require 23 or eight constructors. To that
must be added a no-argument constructor.

B Statically compiled values and Same as row two (one constructor)


C Required constructor parameters

B Statically compiled values and Same as row three


D Optional constructor parameters

C Required constructor parameters and Same as row three except that a minimal
D Optional constructor parameters constructor replaces the no argument
constructor

B Statically compiled values, Same as row three except that a minimal


C Required constructor parameters, and constructor replaces the no argument
D Optional constructor parameters constructor

a. I refer to the constructor that includes all of the required constructor parameters (and only those parame-
ters) as the minimal constructor. Other constructors in the same class will have optional parameters in addi-
tion to the required ones. (The term minimal constructor is of my own making. It is only useful is discussing the
concept of constructor mathematics, and is not to be confused with the more widely used term reference con-
structor discussed below.)

Deciding the order of constructor parameters is also important. Whenever


possible, required constructor parameters should be in the same position in
every constructor in the class. Furthermore, they should always be found to the

FIELDS AND METHODS 47


left of optional constructor parameters in any given parameter list. This rule
makes it easier for client programmers to remember the order of constructor
parameters. Here is an example from above:

public BeanDescriptor(Class beanClass)


public BeanDescriptor(Class beanClass, Class customizerClass)

Imagine how confusing it would be if the parameters in the second constructor


were switched. (Both parameter types are Class.) I would even go as far as to
say that the position of required constructor parameters is implicitly part
of the interface contract.

1.3.3.1 Reference Constructors


Many constructor designs include a reference constructor that is invoked by
some or all of the other constructors in the class in order to avoid redundant
coding of argument checks and initialization logic. I use the term initialization
logic in this context so as to exclude the simple assignment statements such as
this.x = x found in many constructors. It is never necessary to explicitly
invoke another constructor in the same class, if that other constructor does
nothing more than assign the values passed to instance variables using a series
of simple assignment statements such as this.x = x. In fact, redundant cod-
ing of such simple assignment statement is arguably preferable to the use of an
explicit constructor invocation.
The initialization logic in a reference constructor is not necessarily complex,
however. It may be only a few lines of code as in the following example from the
StringBuffer class.
public StringBuffer() {
this(16);
}
public StringBuffer(int length) {
value = new char[length];
shared = false;
}
public StringBuffer(String str) {
this(str.length() + 16);

48 JAVA RULES
append(str);
}

Can you tell which of these is the reference constructor? Is this case, the refer-
ence constructor merely creates an array and marks it as not shared.
Reference constructors are used as a way of assuring that instance initializa-
tion is exactly the same for all of the objects in a class. For example, factory
methods typically use a private reference constructor. Likewise, if there is
no constructor that includes only the initialization logic common to some
or all of the constructors in a class, a private method can be used
instead. Consider the following example from the HashMap class.

public HashMap(int initialCapacity, float loadFactor) {


if (initialCapacity < 0)
throw new IllegalArgumentException(
"Illegal Initial Capacity: " + initialCapacity);
if (loadFactor <= 0)
throw new IllegalArgumentException(
"Illegal Load factor: " + loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
}
public HashMap(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public HashMap() {
this(11, 0.75f);
}
public HashMap(Map t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}

An arguably more efficient implementation would have been the following.

public HashMap(int initialCapacity, float loadFactor) {


if (initialCapacity < 0)
throw new IllegalArgumentException(
"Illegal Initial Capacity: " + initialCapacity);

FIELDS AND METHODS 49


if (loadFactor <= 0)
throw new IllegalArgumentException(
"Illegal Load factor: " + loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
createMap(initialCapacity,loadFactor);
}
public HashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException(
"Illegal Initial Capacity: " + initialCapacity);
if (initialCapacity==0)
initialCapacity = 1;
createMap(initialCapacity, 0.75f);
}
public HashMap() {
createMap(11, 0.75f);
}
public HashMap(Map t) {
createMap(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
private void createMap(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
}

This implementation uses a private method to do the work of a reference


constructor. Doing so eliminates a number of unnecessarily argument checks.

1.3.3.2 Alternative Constructor Designs


Any discussion of alternative constructor designs has the potential of doing
more harm than good. The majority of classes do not require them. In particular,
I think it would be very easy for a novice object-oriented programmer to abuse
factory methods. They are static methods that return as instance of the
class in which they are declared. (Factory methods are always static, so it
does not matter if you refer to them as static factory methods or just fac-
tory methods. The former seems to be the more formal term, but can get very
tedious.) There are a handful of guiding principles that have served me well as a

50 JAVA RULES
computer programmer. One of them is using something the way it was
designed to be used. Doing so tends to avoid unforeseen problems. In this
case, that means using constructors instead of factory methods unless there is
a compelling reason to do otherwise. I urge you to consider doing the same.
Alternative constructor designs solve one of the following problems.
• A need for more than one constructor with the same signature, such as
more than one no-arg constructor
• A class that has an unmanageable number of constructors (when construc-
tor mathematics does indeed become constructor madness ). This always
stems from the fact that there are a lot of what would normally be optional
constructor parameters (as defined in 1.3.3 Constructors) that default if not
specified. Here it is important to differentiate between a class that cannot
know for sure which combinations of optional constructor parameters make
the most sense and a class that simply has an extraordinary number of
optional constructor parameters. The former tends to use set methods
while the later will use a separate class with public fields (a struct-like
data structure) to make it easier for client programmers to set the fields.
Both are discussed further below.
• Constructors are like nameless methods. They can only be differentiated by
their signature. Constructors that serve a highly specialized purpose
(beyond what can be explained by the concept of constructor mathematics)
need a name to make that purpose more explicit to client programmers.
This is precisely why the BigInteger.probablePrime(int
bitLength, Random rnd) factory method was added in the 1.4
release.
The first problem is sometimes solved by rearranging constructor parameters
so that the signatures are arbitrarily different. From an interface design perspec-
tive, that is a really bad idea (and does not address the problem of needing more
than one no-arg constructor).7 A much better solution is to use factory methods.
Here is an example of this use of factory methods from the DateFormat
class:

7. Actually I recall an article in a popular Java magazine that suggested using a dummy parameter
in order to obtain a second no argument constructor. The dummy parameter was of type boolean. It
was suggested that clients always pass true; not that it mattered because the value was completely
ignored. This is a bizarre suggestion and evidences a total disregard for interface design.

FIELDS AND METHODS 51


public final static DateFormat getDateInstance()
public final static DateFormat getDateInstance(int style)
public final static DateFormat getDateInstance(int style,
Locale aLocale)
public final static DateFormat getTimeInstance()
public final static DateFormat getTimeInstance(int style)
public final static DateFormat getTimeInstance(int style,
Locale aLocale)
public final static DateFormat getDateTimeInstance()
public final static DateFormat getDateTimeInstance(int dateStyle,
int timeStyle)
public final static DateFormat getDateTimeInstance(int dateStyle,
int timeStyle,
Locale aLocale)

Note how many of these factory methods would have the same signature if they
were constructors instead. Factory methods are not usually declared final,
which makes it all the more obvious that this is not a traditional use of factory
methods. As discussed in the seminal work Design Patterns,8 the whole point
of using factory methods is to override them in subclasses. Programmers
should be able to readily differentiate the different uses of factory meth-
ods. Here is another example of this particular use (a solution to the problem of
having more than one constructor with the same signature) from the Break-
Iterator class in java.text:

public static BreakIterator getCharacterInstance()


public static BreakIterator getCharacterInstance(Locale where)

public static BreakIterator getWordInstance()


public static BreakIterator getWordInstance(Locale where)

public static BreakIterator getLineInstance()


public static BreakIterator getLineInstance(Locale where)

public static BreakIterator getSentenceInstance()


public static BreakIterator getSentenceInstance(Locale where)

And yet another example from the Logger class in java.util.logging:

8. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns, Elements of
Reusable Object-Oriented Software, (Boston, Addison-Wesley Professional, 1995).

52 JAVA RULES
public static Logger getLogger(String name)
public static Logger getLogger(String name,
String resourceBundleName)

public static Logger getAnonymousLogger()


public static Logger getAnonymousLogger
(String resourceBundleName)

This use of factory methods is very common. The factory methods in the last
two examples can be overridden, but they still have no correspondence whatso-
ever to factory methods as envisioned in Design Patterns. Certainly using fac-
tory methods for something as simple as “constructor names” (the third bulleted
item in the above list of constructor design problems) is not something you will
find discussed in Design Patterns. The essential definition of a factory method,
however, is to “manufacture” an object, and using them to solve constructor
design problems such as these is perfectly valid.
Another example of how the use of factory methods has evolved since the
publication of Design Patterns is their use to return instances of cached immu-
table objects. If there are many requests for the same immutable object, creat-
ing it over and over again in a constructor makes no sense. For example, the API
docs for the valueOf(long unscaledVal, int scale) method in the
BigDecimal class states:
This “static factory method” is provided in preference to a (long,
int) constructor because it allows for reuse of frequently used
BigDecimals.9
That is also why the Boolean.valueOf(boolean b) was added in the 1.4
release. There are many such examples in the core API. The reason for making
these comparisons to Design Patterns is not to discredit the Gang-of-Four (the
GoF, or authors), merely to point out that their ideas have evolved over time
along with the Java programming language.
The problem of too many constructors is usually solved by using a no-arg
constructor and set methods. The GregorianCalendar class comes

9. API docs for the valueOf(long unscaledVal, int scale) method in the
java.math.BigDecimal class

FIELDS AND METHODS 53


readily to mind in this context. There are 17 date and time fields in a Calendar
that can be set by invoking the set(int field, int value) method, not
to mention the time zone and locale parameters. All of these fields are compara-
ble to optional constructor parameters that default if not specified. Using simple
constructor mathematics, had constructors been used in the Calendar class
there would be 219 or 524,288 mathematically possible combinations of con-
structor parameters. The GregorianCalendar class rather judiciously
declares only six of these combinations plus a no-arg constructor:

GregorianCalendar()
GregorianCalendar(int year, int month, int date)
GregorianCalendar(int year, int month, int date,
int hour, int minute)
GregorianCalendar(int year, int month, int date,
int hour, int minute, int second)
GregorianCalendar(Locale aLocale)
GregorianCalendar(TimeZone zone)
GregorianCalendar(TimeZone zone, Locale aLocale)

If other fields need to be set, the set(int field, int value) method is
used after the GregorianCalendar object is created. The Component
and JComponent classes are other important examples of this solution in the
Core API. Though not as immediately obvious, the applyPattern(String
pattern) methods in classes such as DecimalFormat and Simple-
DateFormat are also examples.
Finally, there is the rare case in which a class has an extraordinary number
of optional constructor parameters that default if not specified. The GridBag-
Layout and GridBagConstraints classes in the java.awt package is
a well-known example of this. GridBagLayout is a LayoutManager used
to layout GUI components. The GridBagConstraints class is described as
follows in the API docs.
The GridBagConstraints class specifies constraints for compo-
nents that are laid out using the GridBagLayout class.10

10. API docs for the java.awt.GridBagCnstraints class.

54 JAVA RULES
These “constraints” very well could have been constructor parameters in a differ-
ent solution to the problem of laying out GUI components. Instead, they are
public fields in the GridBagConstraints class, which is basically a C
struct (or record in other legacy systems) in which the value of what would
be constructor parameters can be directly set. The constructor parameters are
then passed to the GridBagLayout class that actually uses them as an
instance of the GridBagConstraints class. All of the public fields in
such a class represent optional constructor parameters that default if not speci-
fied. In the example of the GridBagLayout and GridBagConstraints
classes, the default values are set in a no-argument GridBagConstraints
constructor. The API docs for that no-arg constructor reads as follows.
Creates a GridBagConstraint object with all of its fields set to
their default value.11

Only those fields that do not use the default values need to be set. This also sim-
plifies the overall interface design in that the class that actually uses the con-
structor parameters has only one constructor which is passed an instance of the
class used to set those parameters. Using a separate class in which constructor
parameters are declared as public fields is an extreme solution that I hesitate
to even characterize as an alternative constructor design. Examples such as
GridBagLayout and GridBagConstraints are rare in the core API.

1.4 Field Initialization Anomalies


Field initialization involves a number of anomalies of which Java programmers
should be aware. I have tried to list them in their order of importance. That is,
however, a subjective judgement. Some of the field initialization anomalies dis-
cussed in the following subsections stem from the textual order of variable initial-
izers and initialization blocks. See 2.3.2 The Significance of Textual Order in
Volume 1 for a closely related discussion.

11. API docs for GridBagConstraint() constructor in the java.awt.GridBag-


Constraint class.

FIELDS AND METHODS 55


1.4.1 The Problem of Forward Referencing
Variable initializers and initialization blocks are executed in textual order. The
term textual order simply means the same order in which they appear in the
source code. The implication of executing variable initializers and initialization
blocks in textual order is that it is possible for the code in a special initialization
method to reference a field that has only a standard default value (the NULL
character, zero, false, or a null reference). This is referred to as a for-
ward reference.
Although the field is technically in scope, forward references generate a
compiler error. The following is a rather simple example of what is sometimes
called circular (or cyclic) initialization:

class Test {
int a = b; //COMPILER ERROR
int b = a;
}

Attempting to compile this class generates the following compiler error:

Test.java:2: illegal forward reference


int a = b; //COMPILER ERROR
^
1 error

Note that these are both instance variables.


A class variable can be referenced in an instance variable initializer or
instance initialization block, even when the declaration appears textually after the
reference. For example,

class Test {
int a = b; //NOT A FORWARD REFERENCE
static int b = 10;
}

The difference is that class variables are initialized in the <clinit> method,
which is executed when the class file is loaded. Only class variable initializers
and static initialization blocks can forward reference a class variable.
Method invocation expressions are not checked for forward references. For
example,

56 JAVA RULES
class Test {
static int a = forwardReference();
static int b = 10;
public static void main(String[] args) {
System.out.println(Test.a);
}
static int forwardReference() { return b; }
}

Executing this program prints 0. This is an example of a circular or otherwise


malformed initialization that the compiler cannot detect because they involve
method invocation expressions. The only way to systematically eliminate the
problem of forward referencing involving method invocation expressions would
be to make invoking methods in variable initializers and initialization blocks a
compiler error. However, “Java provides the full power of the language” during
initialization of a class or object. As explained in the JLS,
…the fact that initialization code is unrestricted allows examples to be
constructed where the value of a class variable can be observed when
it still has its initial default value, before its initializing expression is eval-
uated, but such examples are rare in practice…The full power of the
language is available in these initializers; programmers must exercise
some care.12

Care must be exercised, therefore, that the methods invoked in variable initializ-
ers and initialization blocks do not reference a field that is declared further down
in the compilation unit.
Though forward references may involve method invocation expres-
sions, they always in either a variable initializer or an initialization block.
In the case of variable initializers, the problem is always the textual order of field
declarations. Nothing can be done to systematically avoid this problem except
to make sure that if an instance variable is used in an instance variable initializer
(or a class variable is used in a class variable initializer) that it is declared before
the field being initialized. In short, reorder the field declarations. In the case of
initialization blocks, however, the problem of forward referencing can be

12. Gosling et al., §12.4.1, “When Initialization Occurs.”

FIELDS AND METHODS 57


systematically avoided by always coding initialization blocks after fields.
Doing so is somewhat natural because initialization blocks are used to initialize
fields. This is one reason for not coding fields at the very bottom of a compila-
tion unit.
The specification for forward references was substantially changed in the
Second Edition of the JLS as the direct result of Bug Id 4459133, “Forward ref-
erence legal or illegal?” In fact the rather elaborate example from that bug was
copied verbatim into the specification. The original specification read as follows:
A compile-time error occurs if an initialization expression for a class
variable contains a use by a simple name of that class variable or of
another class variable whose declaration occurs to its right (i.e., textu-
ally later) in the same class. 13

A compile-time error occurs if an initialization expression for an


instance variable contains a use by a simple name of that instance vari-
able or of another instance variable whose declaration occurs to its
right (i.e., textually later) in the same class.14

This was replaced by the following specification in the Second Edition.


The declaration of a member needs to appear before it is used only if
the member is an instance (respectively static) field of a class or
interface C and all of the following conditions hold:

• The usage occurs in an instance (respectively static) vari-


able initializer of C or in an instance (respectively static)
initializer of C.
• The usage is not on the left hand side of an assignment.
• C is the innermost class or interface enclosing the usage.
A compile-time error occurs if any of the three requirements above are
not met.15

This specification was not fully implemented until the 1.4 release. I will look at
the last bulleted item first. The necessity for this change can be seen in the fol-
lowing example.

13. Gosling et al., §8.3.2.1, “Initializers for Class Variables.”


14. Gosling et al., §8.3.2.2, “Initializers for Instance Variables.”
15. Gosling et al., §8.3.2.3, “Restrictions on the use of Fields during Initialization.”

58 JAVA RULES
class Test {
public static void main(String[] args) {
new Test().new InnerClass().print();
}
class InnerClass {
void print() {
System.out.println(s);
}
}
String s = "not a forward reference";
}

This program compiles and when executed prints not a forward


reference. The class instance creation expression new Test() effectively
initializes the s instance variable. Moreover, the access is from a different class.
A similar example could be constructed for class variables.
It is the second bulleted item that may surprise some programmers. The fol-
lowing example no longer generates a forward reference compiler error
as the result of this change.

class Test {
static {
s = "this is NOT a forward reference";
}
static String s = "";
}

In light of the discussion in 1.4.4 The Variable Initializer for a Field is Always Exe-
cuted, this change only makes sense. The reality is that fields can be assigned
values after automatic initialization using standard default values and before vari-
able initializers are executed. Why not make that reality more explicit?
It is the following example that intrigues me.

class Test {
static {
s = "this is NOT a forward reference";
String copy = s; //Is this really a forward reference?
}
static String s = "";
}

FIELDS AND METHODS 59


Attempting to compile this program using a 1.4.1 release or later generates the
following compiler error:

Test.java:4: illegal forward reference


String copy = s; //Is this really a forward reference?
^
1 error

Prior to the 1.4.1 release, both statements in the static initialization block
would generate a forward reference compiler error. Now that only the
second one does, I have to question whether this is really a forward reference.
Another approach would be to say that there are no forward references after a
value has been assigned to the field.
Finally, there is an interesting little whole in the compiler analysis for circular
initialization. Here is an example:

class Test {
static final int INLINED_CONSTANT = Test.INLINED_CONSTANT;
public static void main(String[] args) {
System.out.println(INLINED_CONSTANT);
}
}

This example compiles and when executed prints 0. The bytecodes for this
example are even more interesting:

Compiled from Test.java


class Test extends java.lang.Object {
static final int INLINED_CONSTANT;
Test();
public static void main(java.lang.String[]);
static {};
}

Method Test()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 return

Method void main(java.lang.String[])


0 getstatic #2 <Field java.io.PrintStream out>
3 getstatic #3 <Field int INLINED_CONSTANT>

60 JAVA RULES
6 invokevirtual #4 <Method void println(int)>
9 return

Method static {}
0 getstatic #3 <Field int INLINED_CONSTANT>
3 putstatic #3 <Field int INLINED_CONSTANT>
6 return

INLINED_CONSTANT is not in fact inlined. The evaluation for Bug Id 4103280


dismisses this as a “literal interpretation of the spec” (which indeed it is):
This is a literal interpretation of the spec (see 8.3.2.1; the error only
happens for a simple name, and this is not a simple name.) Note that

public static final int INLINED_CONSTANT = INLINED_CONSTANT;

will produce the error the submitter wanted.

It is not trivial to solve the general problem of cyclic initialization. Java


only tries to catch some easy cases.

While this is not a bug (it is a correct implementation of the JLS), one
can imagine a useful optional warning message for such cases. I'm
changing this to an RFE.16 [example modified to conform with mine]

Looking at the byte codes, however, I would say that this should be an error.

1.4.2 Invoking Overridden Methods During Object Initialization


Because of the constructor chaining discussed in 2.4.1.2 Default Constructors
in the Second Edition of Volume 1, the code in <init> methods (after the first
statement, which is always an implicit or explicit constructor invocation except in
the Object class) is executed starting with the baseclass and work down a
class hierarchy to the class of the object being created (emphasis on down).
Superclass variable initialization is thus an extension of the idea of exe-
cuting variable initializers and initialization blocks in textual order. In
other words, instance variable in superclasses and initialized before instance
variables in subclasses. Therefore care must be taken not to execute overridden
instance methods during object initialization. (Note that any method that is not

16. Evaluation of Bug Id 4103280.

FIELDS AND METHODS 61


private , static, or final can be overridden.) This problem is very
closely related to the problem of forward referencing. There is a significant dif-
ference, however, between the following example and the
forwardReference() example in 1.4.1 The Problem of Forward Referenc-
ing.

class Superclass {
Superclass() { overridden(); }
void overridden() { System.out.println("Superclass"); }
}
class Test extends Superclass {
String s = "Test";
public static void main(String[] args) {
new Test().overridden();
}
void overridden() { System.out.println(s); }
}

In both cases the result is a forward reference, but in this example the instance
variable that has only a standard default value (because the variable initializer
has not executed) is declared in a subclass. There is the substantial differ-
ence. Executing this program prints

null
Test

There are two invocations of the overridden() method. The first is from the
Superclass constructor. At that point the <init> method for Test has not
yet been invoked, and all of the instance variables in Test still have standard
default values. Thus null is printed.
In the last example, the overridden() method is invoked in a construc-
tor. The exact same thing happens if you invoke overridden instance methods in
instance variable initializers or instance initialization blocks. For example,

class Superclass {
{ System.out.println(overridden());}
String s = overridden();
String overridden() { return "Superclass"; }
}
class Test extends Superclass {

62 JAVA RULES
String s = "Test";
public static void main(String[] args) {
new Test().print();
}
void print() { System.out.println(super.s); }
String overridden() { return s; }
}

Executing this program prints

null
null

This is not a problem in final classes. In non-final classes, instance meth-


ods invoked during object initialization should be declared either final or
private. Otherwise, you never know when someone may override the method
in a subclass. See also 3.9 Designing Extensible Classes (particularly the discus-
sion of documenting overridable methods).
The clone() and readObject() methods are sometimes referred to
as pseudo-constructors, and are subject to similar problems if they invoke
overridden instance methods.
I sometimes wonder if explaining something does more harm than just pre-
tending like it doesn’t exist. This is one of those times. The problem of invoking
overridden instance methods includes the link variable in inner member classes.
There is an awkward parenthetical reference to this problem in the original Inner
Classes Specification:
(Note: There is a limitation in some implementations of Java 1.1, under
which the initialization of this$0 is delayed until after any superclass
constructor is run. This means that up-level references made by a sub-
class method may fail if the method happens to be executed by the
superclass constructor.)17

Apparently the author did not realize this problem would not go away. Further-
more, it is not limited to “some implementations.” It is just the problem of invok-

17. John Rose, Inner Classes Specification (Mountain View: Sun Microsystems, 1997), “How do
inner classes work?” As I am reading him, “up-level reference” in this context means access to the
private members of an enclosing class. It must mean that because this$0 (otherwise known
as a link variable) is only used to defeat the normal access control mechanism in a JVM

FIELDS AND METHODS 63


ing overridden instance variables during object initialization as applied to inner
member classes. For example,

class Superclass {
class InnerMemberSuperclass {
InnerMemberSuperclass() { print(); }
void print() {System.out.println("overridden method");}
}
}
class Test extends Superclass {
String s = "private member";
public static void main(String[] args) {
new Test().new InnerMemberSubclass();
}
class InnerMemberSubclass extends InnerMemberSuperclass {
void print() {System.out.println(s);}
}
}

Attempting to execute this program throws a NullPointerException


because the link variable in InnerMemberSubclass that allows it to access
the instance variable s in Test has not yet been initialized. (There is a detailed
explanation in the next paragraph). The same thing would happen if the overrid-
den instance method were invoked in a variable initializer or instance initialization
block in InnerMemberSuperclass.
As explained in 2.8.2 Full Access to the Members of an Enclosing Class, link
variables (referred to as this$0 in the above quote from the Inner Classes
Specification) are instance variables initialized in the constructor of an inner
member class. The compiler generated code for the Test and InnerMem-
berSubclass looks like this:

class Test {
private String s = "this is a private instance variable";
public static void main(String[] args) {
new Test().new Test$InnerMemberClass().print();
}
String access$000() { //COMPILER-GENERATED ACCESSOR METHOD
return s; //DEFEATS NORMAL ACCESS CONTROL
}
}

64 JAVA RULES
class Test$InnerMemberSubClass {
private Test this$0; // COMPILER-GENERATED LINK VARIABLE
// JVM QUIETLY PASSES BOTH this AND this$0
Test$InnerMemberClass (Test this$0) {
this.this$0 = this$0;
}
void print() {System.out.println(this$0.access$000());}
}

Now you can see more clearly why a NullPointerException is thrown.


The link variable this$0 in InnerMemberSubclass has not yet been initial-
ized when the print() method is invoked in InnerMemberSuperclass.

1.4.3 Inlined Constants Always Appear to Have Been Initialized


Inlined constants are discussed in 1.5.2 Inlined Constants. They are final vari-
ables initialized with compile-time constant expressions. The specification for
inlined constants reads in part,
…such constant fields must always appear to have been initialized; the
default initial value for the type of such a field must never be
observed.18

The effect of this specification is that inlined constants are not subject to the
field initialization anomalies discussed in the previous two sections. To demon-
strate this fact, I will use examples from those sections and modify them to use
inlined constants. Here is the first of the two examples from 1.4.1 The Problem
of Forward Referencing:

class Test {
static int a = forwardReference();
static final int b = 10;
public static void main(String[] args) {
System.out.println(Test.a);
}
static int forwardReference() { return b; }
}

18. Gosling et al., The Java Language Specification, §13.1, “The Form of a Binary.”

FIELDS AND METHODS 65


Executing this program prints 10. It used to print 0 before adding the final
modifier to the declaration of b (thereby making it an inlined constant). Here is
the example from 1.4.2 Invoking Overridden Methods During Object Initialization
(also modified by adding final ):

class Superclass {
Superclass() { overridden(); }
void overridden() { System.out.println("Superclass"); }
}

class Test extends Superclass {


final String s = "Test";
public static void main(String[] args) {
new Test().overridden();
}
void overridden() { System.out.println(s); }
}

Executing this program prints

Test
Test

This program used to print null followed by Test before adding the final
modifier to the declaration of s (thereby making it an inlined constant).
There is a question as to how the inlined constants “must always appear to
have been initialized” specification is implemented. The JLS very misleadingly
suggests that this specification is implemented by merely initializing inlined con-
stants before the other fields in a class:
One subtlety here is that, at run time, static variables that are
final and that are initialized with compile-time constant values are ini-
tialized first. This also applies to such fields in interfaces. These vari-
ables are “constants” that will never be observed to have their default
initial values, even by devious programs.19

I have decompiled a lot of code study the implementation of this specification.


What I can tell you with some certainty is the if the inlined constant is an instance

19. Gosling et al., §8.3.2.1, “Initializers for Class Variables.” There is a similar statement in refer-
ence to interface constants in §9.3.1, “Initialization of Fields in Interfaces.”

66 JAVA RULES
variable initialization is not reordered. That does not matter, however, because
the value of the inlined constant is never “observed.” It is inlined. In the case of
class variable, while the declaration of the static field is written to the class
file, but there appears to be no initialization logic at all. The reordering of initial-
ization is therefore a moot point. Yet elsewhere the JLS clearly says that there
should no references to an inlined constant “present in the code in a binary file
(except in the class or interface containing the constant field, which will have
code to initialize it).…20 Here is an example,

class Test {
String one = "1";
String two = "2";
final String THREE = "3"; //inlined instance variable
static final String FOUR = "4"; //inlined class variable
{
String s = THREE;
}
static {
String s = FOUR;
}
}

The decompiled code for the Test class is as follows:

Compiled from Test.java


class Test extends java.lang.Object {
java.lang.String one;
java.lang.String two;
final java.lang.String THREE;
static final java.lang.String FOUR;
Test();
static {};
}

Method Test()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 aload_0

20. Gosling et al., The Java Language Specification, §13.1, “The Form of a Binary.” Emphasis
added.

FIELDS AND METHODS 67


5 ldc #2 <String "1">
7 putfield #3 <Field java.lang.String one>
10 aload_0
11 ldc #4 <String "2">
13 putfield #5 <Field java.lang.String two>
16 aload_0
17 ldc #6 <String "3">
19 putfield #7 <Field java.lang.String THREE>
22 ldc #6 <String "3">
24 astore_1
25 return

Method static {}
0 ldc #8 <String "4">
2 astore_0
3 return

The lines in bold show that THREE and FOUR are indeed inlined constants (there
are no references to these fields in the decompiled code, only their values). As
you can see, THREE is not initialized before the other instance variables. There
is no reordering of the initialization as suggested in the above quote from the
JLS. The declaration of FOUR is written to the file, but not the variable initializer.
None of this, however, changes the fact that an inlined constant “always appear
to have been initialized.” It is just a question of how this specification is imple-
mented.

1.4.4 The Variable Initializer for a Field is Always Executed


It is easy to assume that a compiler would optimize away a variable initializer
that evaluates to the standard default value for that type. For example:

int x = 0;

Why should the compiler include the assignment statement x = 0 in the


<init> method for this class? The reason is that fields can be assigned a
different value after automatic initialization using standard default val-
ues and before the variable initializer is executed. For example,

class Test {
public static void main(String[] args) {
System.out.println(i);

68 JAVA RULES
}
static {
i = Integer.MAX_VALUE;
}
static int i = 0;
}

Executing this program prints 0. If the variable initializer were not executed it
would print the value of Integer.MAX_VALUE.
According to 8.3.2.3, “Restrictions on the use of Fields during Initialization”
in the JLS, this is not an example of forward referencing (which is somewhat
obvious because otherwise it would not have compiled). That section was added
to the Second Edition of the JLS and reflects a change in the Java programming
language that was not actually implemented until the 1.4.1 release. The fact that
“the variable initializer for a field is always executed” is an important change in
the Java programming that was implemented in the 1.2 release, however.21 The
rationale for the change at the time was in fact forward references and the
closely related problem of invoking overridable methods during object initializa-
tion. Prior to the 1.2 release, the next two examples would print 100, whereas
now they print 0.

class Test {
int j = forwardReference();
int i = 0;
int forwardReference() {
return i = 100;
}
public static void main(String[] args) {
System.out.println(new Test().i);
}
}

The second example involves invoking overridden instance methods during


object initialization:

21. I became aware of this issue while reading the “Compatibility with Previous Releases” document
in the 1.2 release. For some reason the primary Bug Id 1227855 is no longer available on the Bug
Database.

FIELDS AND METHODS 69


class Superclass {
Superclass() { overridden(100); }
void overridden(int value) { };
}
class Test extends Superclass {
int i = 0;
void overridden(int value) { i = value; }
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.i);
}
}

The assignment statement i = 0 simply was not included in the <init> meth-
ods for both of these examples because of the assumption that doing so would
overwrite the standard default value. Similar examples can be constructed for
class variables and reference type fields that are initialized with the standard
default value of null. This change is significant in part because it means explic-
itly initializing fields with their standard default value is not without cost.

1.4.5 StackOverflowError During Object Initialization


Instance variable initializers, instance initialization blocks, and constructors can-
not instantiate the class in which they are declared, nor any subclasses thereof.
For example,

class Test {
public static void main(String[] args) {
new Superclass();
}
}
class Superclass {
Subclass subclass = new Subclass();
}
class Subclass extends Superclass { }

Attempting to execute this program throws the following error:

Exception in thread "main" java.lang.StackOverflowError


at Subclass.<init>(Test.java:9)
at Superclass.<init>(Test.java:7)
at Subclass.<init>(Test.java:9)

70 JAVA RULES
at Superclass.<init>(Test.java:7)
at Subclass.<init>(Test.java:9)
at Superclass.<init>(Test.java:7)

StackOverflowError usually indicates an unintended loop (though it may


be thrown by a deeply recursive algorithm that exceeds the maximum stack size
before completing). In this case, every time there is an attempt to instaniate an
instance of the same class (which includes subclasses) in an <init> method,
the class instance creation process starts over. I like to think of this as the hall
of mirrors effect. It is much easier to see in Figure 1.1. More and more activa-
tion frames are added to the stack in step #4 in order to execute the <init>
methods for the new Subclass() variable initializer evaluated in step #7 until
finally the maximum stack size is exceeded.
It is important to realize that the restriction against instantiating the same
class applies only to the <init> method (instance variable initializers, instance
initialization blocks, and constructors). The <clinit> method (class variable
initializers and static initialization blocks) will not loop if the same class is
instantiated. In fact, class variables are routinely initialized with instances of the
class in which they are declared. The Singleton design pattern is a classic exam-
ple of doing so. See also 2.3.5 Circular Dependencies in Type Declarations.

FIELDS AND METHODS 71


Figure 1.1 The Class Instance Creation Process

72 JAVA RULES
1.1
NOTE Every once in a while I come across some of my own technical writing
that makes absolutely no sense (even to me) and is obviously wrong.
While this is never a pleasant experience, there is usually some reason
for the confusion. While working on the index, that is exactly what hap-
pened with the following section on August 14, 2003. It should be com-
pletely ignored in all versions of the book published prior to that date.
The problem in this case was a huge compiler bug that I immediately
submitted. Actually, I am sure that it will result in a change to the spec-
ification rather than the language because otherwise there will be an
equally huge backwards compatibility problem.

1.4.6 Throwing Checked Exceptions During Initialization


Unlike static initialization blocks, instance initialization blocks can throw
checked exceptions, but only if the exception is included in the throws clause
of every constructor in the class. As explained in the original Inner Classes
Specification (which introduced instance initialization blocks to the language):
An instance initializer may not return, nor throw a checked exception,
unless that exception is explicitly declared in the throws clause of each
constructor. An instance initializer in an anonymous class can throw
any exceptions.22

Note that this specification never saw the light of day until the 1.4 release.23
Uncharacteristically, the Second Edition of the JLS included the following
very thoughtful explanation for the difference between instance initialization
blocks in named versus unnamed (or anonymous) classes.
An instance initializer of a named class may not throw a checked
exception unless that exception or one of its superclasses is explicitly
declared in the throws clause of each constructor of its class and the

22. Rose, “Other changes too the Java 1.1 language.”


23. The primary Bug Id 4054256 was submitted on May 23, 1997. To make matters worse, this
bug was marked “Closed, fixed” long before it actually was.

FIELDS AND METHODS 73


class has at least one explicitly declared constructor. An instance ini-
tializer in an anonymous class can throw any exceptions.

The rules above distinguish between instance initializers in named and


anonymous classes. This distinction is deliberate. A given anonymous
class is only instantiated at a single point in a program. It is therefore
possible to directly propagate information about what exceptions might
be raised by an anonymous class instance initializer to the surrounding
expression. Named classes, on the other hand, can be instantiated in
many places. Therefore the only way to propagate information about
what exceptions might be raised by an instance initializer of a named
class is through the throws clauses of its constructors. It follows that a
more liberal rule can be used in the case of anonymous classes. Simi-
lar comments apply to instance variable initializers.24

The first paragraph is a restatement of the original in the Inner Classes Speci-
fication. The second paragraph is an elaborate explanation as to why instance
initialization blocks in an anonymous class can throw any checked exception. In
plain English, anonymous classes are only used by the programmers who code
them. Therefore, there is no need for a throws clause.
Although an undocumented feature of the language, instance variable initial-
izers behave exactly the same as instance initialization blocks in this regard;
they can throw checked exceptions so long as the exception is assignable
to an exception class in the throws clause of every constructor in the
class (which assumes that at least one constructor is declared because default

The fact that instance variable initializers in named classes can also
throw checked exceptions is an undocumented feature of the Java
programming language.

constructors do not throw exceptions). On August 14, 2003, I submitted the fol-
lowing bug which will explain this in detail.

24. Gosling et al., §8.6 Instance Initializers

74 JAVA RULES
I am sure this is a documentation problem in the JLS, but am filing it
against the compiler because it clearly contradicts the Second Edition
of the JLS.

Amazingly, I have not been able to find any previously submitted bugs
or any discussion of this issue whatsoever on the JDC.

While it is generally understood that instance initializers (as defined in


JLS §8.6, “Instance Initializers”) can throw checked exceptions if the
checked exception is assignable to an exception class in the throws
clause of every constructor in the class (which assumes that at least
one constructor is declared), the same cannot be said for instance vari-
able initializers. Here it is important to distinguish between instance ini-
tializers and instance initialization methods (or <init> methods). The
sections of the JLS that I will cite in the remainder of this bug report are
clearly discussing the former, which is a free standing block of code in
compilation units as opposed to the methods invoked by the JVM to ini-
tialize objects. The code from instance initializers is included in
<init> methods, but these are two very different entities.
The specification in question is the following paragraph from §8.3.2,
“Initialization of Fields” in the Second Edition of the JLS.

It is a compile-time error if the evaluation of a variable initializer


for a static field or for an instance variable of a named class
(or of an interface) can complete abruptly with a checked
exception.

This specification is repeated in §11.2, “Compile-Time Checking of


Exceptions”:

“Static initializers, class variable initializers, and instance initial-


izers or instance variable initializers within named classes and
interfaces, must not result in a checked exception; if one
does, a compile-time error occurs. No such restriction applies
to instance initializers or instance variable initializers within
anonymous classes.”

This paragraph from §11.2, “Compile-Time Checking of Exceptions”


involves an unrelated error that has been corrected at http://

FIELDS AND METHODS 75


java.sun.com/docs/books/jls/clarifications-2-2nd-
ed.html as follows.
JLS 11.2

The last two paragraphs of section 11.2 on page 221 should


be amended to read. This resolves an apparent discrepancy
with JLS 8.6:

Static initializers, class variable initializers, and instance vari-


able initializers within named classes and interfaces, must not
result in a checked exception; if one does, a compile-time
error occurs. No such restriction applies to instance initializers
or instance variable initializers within anonymous classes.

An instance initializer of a named class may not throw a


checked exception unless that exception or one of its super-
classes is explicitly declared in the throws clause of each con-
structor of its class and the class has at least one explicitly
declared constructor. An instance initializer in an anonymous
class can throw any exceptions.”

Here I must point out that only the last paragraph of that section is
being corrected. The second paragraph is an addition to the printed
specification.

In all three JLS passages we find the following two sentences:

It is a compile-time error if the evaluation of ... an


instance variable of a named class (or of an interface)
can complete abruptly with a checked exception.
(§8.3.2, “Initialization of Fields”)

...instance variable initializers within named classes and


interfaces, must not result in a checked exception; if
one does, a compile-time error occurs. (§11.2, “Compile-
Time Checking of Exceptions” and http://
java.sun.com/docs/books/jls/
clarifications-2-2nd-ed.html)
These specification could not be any clearer. Instance variable initializ-
ers in named classes cannot throw checked exceptions, but that is not
how the language actually works. Consider the following program:

76 JAVA RULES
class Test {
String s = doSomething();
Test() throws Exception { }
String doSomething() throws Exception {
return "throws a checked exception";
}
}

The method invocation doSomething() is clearly an instance vari-


able initializer in a named class that throws a checked exception.

This program compiles in the 1.4.2 release as well as in all previous


releases back to the 1.2 release in which it breaks the compiler. Only in
1.1.8 and earlier releases does it generate the following compiler
error:

Test.java:2: Exception java.lang.Exception can't be thrown in


initializer.
String s = doSomething();
^
1 error

It is clear that the language changed sometime between the 1.1.8 and
the 1.3 releases. The change is that instance variable initializers are
behaving the same instance initializers in this regard. They can throw a
checked exception as long as the exception is assignable to an excep-
tion class in the throws clause of every constructor in the class (which
again assumes that at least one constructor is declared).

Further confirmation of this behavior is possible by simply deleting the


throws clause in the constructor (or deleting the entire constructor for
that matter). If this is done, all releases back to the 1.2 release gener-
ate the following compiler error:

Test.java:2: unreported exception java.lang.Exception; must


be caught or declared to be thrown
String s = doSomething();
^
1 error

This is confirmation in and of itself that the compiler behavior is deliber-


ate.

FIELDS AND METHODS 77


Is this a compiler error or a documentation problem? At this point the
answer almost has to be that the JLS must be changed. Otherwise,
there is a backwards compatibility problem. Yet there is a strong argu-
ment against permanently adopting this change to the language. (I
really do not care what is done but present this as a devil's advocate
argument.)

Language designers usually opt for the greatest flexibility as long as


there is no downside. In this case that would have been specifying that
instance variable initializers for named classes can throw any checked
exception as long as it is named in the throws clause of every construc-
tor in the class (the same rule as is applied to instance initialization
blocks). There is no significant difference between the two. Class
instance creation expressions do not actually invoke a constructor,
they invoke <init> methods. These special initialization methods
include code from instance variable initializers (transformed into assign-
ment statements) and instance initialization blocks, as well code from
the corresponding constructor. The <init> method copies the
throws clause from the corresponding constructor. Thus adding
checked exceptions thrown in instance variable initializers and instance
initialization blocks to the throws clause of every constructor in the
class is an obvious means of documenting that instantiating the class
(i.e., evaluating a class instance creation expression) throws checked
exceptions above and beyond what the constructor throws. Where is
the downside? The downside is that there is no possibility of catching
the exception thrown in an instance variable initializer. Thus, allowing
them to throw checked exceptions encourages a coding style in which
checked exceptions are added to the throws clause of every construc-
tor in the class simply because the programmer either does not realize
that the exception could be handled in an instance initializer or is to lazy
to do so.

As of this writing, I have not yet received a reply to this bug submission. It could
be that I am wrong, but it looks pretty straightforward to me.
While on the subject of instance variable initializers, there is a minor error in
the Second Edition of the JLS. The last paragraph in 11.2 Compile-Time Check-
ing of Exceptions read as follows.

78 JAVA RULES
Variable initializers for fields and static initializers must not result in a
checked exception; if one does, a compile-time error occurs.25

Bracha changed it to read:


Static initializers, class variable initializers, and instance initializers
or instance variable initializers within named classes and interfaces,
must not result in a checked exception; if one does, a compile-time
error occurs. No such restriction applies to instance initializers or
instance variable initializers within anonymous classes.26 [emphasis
added]

Based on the rather thoughtful explanation above, he obviously knew that


instance initialization blocks (here referred to as instance initializers) in named
classes can throw checked exceptions (as long as they are included in the
throws clause of every constructor in the class). This amounts to little more
than a typo, and has been fixed in the “Clarifications and Amendments to The
Java Language Specification, Second Edition” document.27
There are two reasons why class variable initializers and static initializa-
tion blocks (read the <clinit> method) cannot throw checked exceptions:
• The JVM cannot be interrupted while loading a class. Such a transfer of
control would leave the class partially loaded and unusable should the
exception be caught (or more precisely, not result in abnormal program ter-
mination).
• There is no possibility of recovery. The class or interface initialization
method <clinit> can be executed at any time (whenever a class or
interface is loaded as the result of the first active use28). Consequently,
there is no way for a Java programmer to catch and handle exceptions
thrown during class initialization.

25. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
§11.2, “Compile-Time Checking of Exceptions.” (Do not update.)
26. Gosling et al., §11.2, “Compile-Time Checking of Exceptions.”
27. See java.sun.com/docs/books/jls/clarifications-2-2nd-ed.html.
28. This is a somewhat complex term that is defined in an as yet unpublished volume of Java Rules.
The first active use of a class or interface type is defined by §5.5, “Initialization” in the JVMS. Note
that in response to Bug Id 4073950, “invocation of certain reflective methods” (in particular, the
Class.forName(String className) is now included in the definition of first active use.

FIELDS AND METHODS 79


This is precisely why <clint> methods are designed to only throw errors.
Errors generally are not caught. If an exception is thrown during class initializa-
tion (any exception, including runtime exceptions), the exception is caught and
translated into an ExceptionInInitializerError. The specification for
this error reads as follows.
If a throw statement is contained in a static initializer, then a compile-
time check ensures that either its value is always an unchecked excep-
tion or its value is always caught by some try statement that contains
it. If at run-time, despite this check, the value is not caught by some
try statement that contains the throw statement, then the value is
rethrown if it is an instance of class Error or one of its subclasses;
otherwise, it is wrapped in an ExceptionInInitializerError
object, which is then thrown.29

Note that this says “instance of class Error or one of its subclasses.” As
stated above, even instances of RuntimeException are translated into an
ExceptionInInitializerError. For example,

class Test {
static { throwsRuntimeException(); }
public static void main(String[] args) {
System.out.println(“Hello World!”);
}
static void throwsRuntimeException() {
throw new RuntimeException();
}
}

Attempting to execute this program throws the following exception:

Exception in thread "main" java.lang.ExceptionInInitializerError


Caused by: java.lang.RuntimeException
at Test.throwsRuntimeException(Test.java:7)
at Test.<clinit>(Test.java:2)

Only errors escape the ExceptionInInitializerError wrapper.

29. Gosling et al., The Java Language Specification, §14.17, “The throw Statement.”

80 JAVA RULES
Notice that the original exception is included in the stack trace printed. This
was not the case before the Java 1.2 release. To determine the cause of the
problem in earlier releases, the getException() method in Exception-
InInitializerError had to be invoked. This change in the 1.2 release of
the ExceptionInInitializerError class became the model for print-
ing chained exceptions in the 1.4 release.

1.5 Constants
Variables declared final are constants. The fields in an interface are implic-
itly final, which explains why they are usually referred to as interface con-
stants. After initialization, the value of a constant cannot be changed. If a
constant is used in a context that requires a variable, such as the left-hand oper-
and of an assignment expression (except for the initialization of a blank final
as discussed 1.5.4 Blank Finals), a compiler error is generated. For example,

class Test {
static final String s = "cannot be changed";
public static void main(String[] args) {
s = "compiler error";
}
}

Attempting to compile this program generates the following compiler error:

Test.java:4: cannot assign a value to final variable s


s = "compiler error";
^
1 error

If the variable initializer is removed from the declaration of s it becomes a blank


final. Only then can it be assigned a value elsewhere in the program.
The meaning of the final modifier is fundamentally different for fields and
methods. For methods, the final modifier means subclasses cannot declare a
method with the same signature. However, subclasses can declare a field with
the same name as what would otherwise be an inherited superclass field
declared final. Furthermore, the type of the subclass field does not have to

FIELDS AND METHODS 81


be the same as the hidden superclass field. One field can even be static and
the other non- static. For example,

class Test {
final int ZERO = 0; //instance varaible
public static void main(String[] ars) {
System.out.println(Subclass.ZERO);
}
}
class Subclass extends Test {
static final String ZERO = "zero"; //class varaible
}

This program compile and when executed prints one. Hiding is discussed at
length in the next chapter.
The subject of constants is much more complex than simply declaring a field
or local variable final, however, and is thoroughly discussed in the following
subsections.

1.5.1 Compile-Time Constant Expressions


Understanding compile-time constant expressions is critical. Every Java pro-
grammer should be able to look at source code and determine what is and what
is not a compile-time constant expression. The following examples are from the
JLS.

true
(short)(1*2*3*4*5*6)
Integer.MAX_VALUE / 2
2.0 * Math.PI
"The integer " + Long.MAX_VALUE + " is mighty big."30

Some of these examples are highly contrived. More often than not, compile-time
constant expressions are just literals.
The term constant expression has a very precise meaning in the Java pro-
gram language, as explained in this section. It is not to be confused with con-
stants in general (variables declared final). Perhaps because the more

30. Gosling et al., §15.28, “Constant Expression.”

82 JAVA RULES
general term constant is so easily confused with constant expression, the JLS
prefers the more formal term compile-time constant expression.31 Using the
term constant to refer to compile-time constant expressions, as is done by a
number of software engineers and technical writers, is extremely confusing
because not all variables declared final (which is the general meaning of the
term constants) are compile-time constant expressions.
Understanding the definition of compile-time constant expressions in the
Java programming language is important for the following reasons (which I have
tried to list in their order of importance).32 While reading this list, it is important
to know that inlined constants (which are discussed in the next section) are
final variables initialized with the value of a compile-time constant
expression.
• The case labels in a switch statement must be constant expressions. This
is the most “in your face” use of compile-time constant expressions for the
average programmer. See 4.8.1.1 The switch Statement for a discus-
sion.
• Inner classes can declare constants, but only if they are inlined.
• References to inlined constants from other classes and interfaces do not
cause the class or interface in which they are declared to be loaded, linked,
and initialized. In other words, references to inlined constants does not con-
stitute the “first active use” of a class or interface.
• If the operands in a string concatenation operation are not compile-time con-
stant expressions, the string concatenation operation must be executed at
runtime. This is the definition of a computed string as discussed in 5.11
String Concatenation Operations in Volume 1.
• The general expectation in conditional compilation is that debugging code is
not written to class files if the debug flag is set to false. In order for

31. Though not used in 15.28 Constant Expressions, the more formal compile-time constant
expression is used everywhere else in the JLS.
32. I had been working on this list on my own for several years, but some additions were discovered
(and elaborated upon) while reading the unofficial “Java Spec Report” at www.ergnosis.com/
java-spec-report/java-language/jls-15.28-e.html as well as Bug Id 4396260 (includ-
ing some of the insightful comments). All such lists are built by searching for either “constant” or
“constant expression” in the JLS. However, you can take this too far. There seems to be a consen-
sus that the list as shown here is complete.

FIELDS AND METHODS 83


debug code to not be written to a class file, however, the debug flag must
be an inlined constant. That in turn requires recompiling all of the classes
that use the debug flag whenever the value changes (which is not usually a
problem because debug flags are usually package-private). Furthermore, an
inlined debug flag cannot be used as the Boolean expression in a for or
while statement. Doing so will generate an unreachable statement com-
piler error when the flag is set to false. For example,
class Test {
public static void main(String[] args) {
final boolean debug = false;
while (debug)
System.out.println("unreachable statement");
for (;debug;)
System.out.println("unreachable statement");
do {
System.out.println("reachable statement");
} while (debug);
}
}

This example does not compile because of the unreachable statements.


Thus declaring a debug flag final and initializing it with the
boolean literal true or false is a critically important decision.
• The type of a conditional expression (the ?: operator) may be byte,
short , or char if either the second or third operand “is of type T where T
is byte, short, or char, and the other operand is a constant expres-
sions of type int whose value is representable in type T.”33 This specifica-
tion is very closely related to the definition of implicit narrowing
conversions in 5.7.1 Simple Assignment Conversion Context. Both allow
numeric literals to be treated as if they were a smaller integer type.
• The field initialization anomalies of forward referencing and invoking overrid-
den instance methods in a superclass <init> method do not apply to
inlined constants (as discussed in 1.4.3 Inlined Constants Always Appear to
Have Been Initialized).
• A compile-time constant expression is always FP-strict.

33. Gosling et al., The Java Language Specification, §15.25, “Conditional Operator ? :”

84 JAVA RULES
As you can see, this is an extensive list. This is why I say it is important for all
Java programmers to understand the definition of compile-time constant expres-
sions.
Compile-time constant expressions are defined in terms of what they can
include, which is any of the following.
• Any literal other than the null literal or class literals.
• The simple name or TypeName.fieldName references to inlined con-
stants.34
• Any unary, binary, and ternary operator other than the increment or decre-
ment operators (prefix or postfix) and assignment operators (simple or com-
plex). These are the only unary, binary, or ternary operators that require a
variable. There are only two operators that are not unary, binary, or ternary.
They are instanceof and cast operators. The instanceof operator
cannot be used. Cast operators can be used, but only if the type name in
the cast operator is a primitive data type or String.
• Parentheses that enclose a compile-time constant expression as defined by
the previous bulleted items.
Constant expressions can also be defined in terms of what they do not include. A
constant expression cannot include any value that could possibly change after
compilation.

Constant expressions have no variables, no method invocation


expressions, and no reference types other than String.

Here I am using the strict definition of variables, which does not include vari-
ables declared final . (Is that a confusing sentence or what?)
The remainder of this section discusses a documentation problem that has
plagued compile-time constant expressions. Is null a compile-time constant
expression or not? The short answer is No, but there is some interesting history

34. There is an exception for TypeName.fieldName references involving circular initialization.


See www.ergnosis.com/java-spec-report/java-language/jls-15.28-d.html for a
discussion.

FIELDS AND METHODS 85


here. The “Clarifications and Amendments to The Java Language Specifica-
tion” document published for the original JLS included the following:
null is a Compile time Constant
JLS 15.27 defines the notion of a compile time constant. The definition
erroneously excludes null. The beginning of JLS 15.27 should read:

A compile-time constant expression is an expression denoting


a value of primitive type or null or a String that is com-
posed using only the following:

* Literals of primitive type, null and literals of type String35


This change was not included in the Second Edition of the JLS, as can be seen in
the following example:

class Test {
static final Object NOT_INLINED = null;
void test() {
Object obj = NOT_INLINED;
}
}

The decompiled code for the test() method is as follows:

Method void test()


0 getstatic #2 <Field java.lang.Object NOT_INLINED>
3 astore_1
4 return

As you can see, null is clearly not inlined. That stands is stark contrast to Bug
Id 4083093, which is mark “Closed, fixed” and includes the following evaluation.
The spec needs to be corrected. As xxxxx@xxxxx says, javac has
always treated null as a compile time constant.

The full extent of this documentation problem can be seen in the following code
from the System class.

35. Unascribed, “Clarifications and Amendments to The Java Language Specification” available
online at java.sun.com/docs/books/jls/clarify.html, (Mountain View: Sun Microsys-
tems, 1995-2003), “null is a Compile time Constant.”

86 JAVA RULES
public final static InputStream in = nullInputStream();
public final static PrintStream out = nullPrintStream();
public final static PrintStream err = nullPrintStream();

/**
* The following two methods exist because in, out, and err
* must be initialized to null. The compiler, however, cannot
* be permitted to inline access to them, since they are later
* set to more sensible values by initializeSystemClass().
*/
private static InputStream nullInputStream() throws
NullPointerException {
if (currentTimeMillis() > 0)
return null;
throw new NullPointerException();
}
private static PrintStream nullPrintStream() throws
NullPointerException {
if (currentTimeMillis() > 0)
return null;
throw new NullPointerException();
}

If this strikes you as odd, the reason in part is that this code has been in place
since before blank finals were introduced to the language in the 1.1 release. The
problem I have with this code is that InputStream and PrintStream are
reference types other than String (the values of which are never inlined), but
this code does make me wonder if null was in fact inlined at some point in the
evolution of the Java platform. The explanation you are about to read as to why
null cannot be inlined would suggest that the answer is No, but then again this
is the System class. One assumes the responsible programmer is in the know.
One thing is for sure, however, is that you should not emulate this code. Just use
a blank final if the need arises.
Apparently in dealing with this problem during the writing of the Second Edi-
tion of the JLS, the decision was made that null is not a compile-time constant
expression. The only explanation I can find for this is at the bottom of Bug Id
4475252, which includes the following comment.
null is not a constant expression because its value cannot be
expressed in the VM's constant pool.

FIELDS AND METHODS 87


According to the unofficial “Java Spec Report” (a resource I discovered while investi-
gating this problem), the problem is that null cannot be represented in the
ConstantValue attribute in a class file.36 More specifically, entries in the
constant_pool of a class file are currently limited to CONSTANT_Integer,
CONSTANT_Long , CONSTANT_Float , and CONSTANT_Double, and
CONSTANT_String. The implementation of null as a compile-time constant
expression would require the addition of a CONSTANT_Null to the class file for-
mat. Such a change is unlikely to occur except possibly in a major release.
As a final note (a rather obscure one), in pre-1.2 releases the javac compil-
ers would throw an ArithmeticException while trying to compute the
value of an inlined constant initialized with the value of 1/0. In order to solve this
problem two things were done. First the decision was made to write the division
operation to the class file. For example,

Test {
final int i = 1 / 0;
}

Here are the decompiled bytecodes:

Compiled from Test.java


class Test extends java.lang.Object {
final int i;
Test();
}

Method Test()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 aload_0
5 iconst_1
6 iconst_0
7 idiv
8 putfield #2 <Field int i>
11 return

36. See www.ergnosis.com/java-spec-report/java-language/jls-15.28-b.html.

88 JAVA RULES
The compiler is writing code that it knows for sure will throw an error, but there
is nothing unusual about that. The other thing they had to do was to change the
specification for inlined constants. The following was added to the “Clarifications
and Amendments to The Java Language Specification, Second Edition” docu-
ment.
15.28

The first two lines of this section should be amended to read:

A compile-time constant expression is an expression denoting a


value of primitive type or a String that does not complete
abruptly and is composed using only the following:
Note that this implies that 1/0 is not a compile time constant. Such an
expression, if evaluated, throws an appropriate exception at runtime.37

One of the reasons I decided to include the 1/0 discussion at the bottom of this
section is to make a point about the JLS. Sometimes the quest for succinct-
ness comes at a very high cost. This, I believe, is a perfect example. Without
any further explanation in the specification, how many man hours will be lost by
programmers trying to figure the significance of “that does not complete
abruptly” in the above specification? However many that may be, they could be
wiped away with a simple footnote. All that it need do is reference the primary
Bug Id 4178182. Am I the only one that questions the wisdom of these kind of
trade-offs?

1.5.2 Inlined Constants


The value of some (but not all) fields declared final and that are either a prim-
itive numeric type or String are inlined by compilers when class files are writ-
ten. Why is the value of some final fields inlined and not others? The
difference is the variable initializer. If the variable initializer is a compile-time con-
stant expression such as a literal (other than null or a class literal), then the

37. Unascribed, “Clarifications and Amendments to The Java Language Specification, Second Edi-
tion” available online at java.sun.com/docs/books/jls/clarifications-2-2nd-
ed.html, (Mountain View: Sun Microsystems, 1995-2003), “15.28.”

FIELDS AND METHODS 89


value of the field is inlined rather than using a field access expression (the
getfield or getstatic machine instructions). For example,

class Test {
static final String INLINED = "value";
static final String NOT_INLINED = getString();
public static void main(String[] args) {
String s = INLINED;
s = NOT_INLINED;
}
static String getString() {
return "value";
}
}

The decompiled code for the main method is as follows:

0 ldc #2 <String "value">


2 astore_1
3 getstatic #3 <Field java.lang.String NOT_INLINED>
6 astore_1
7 return

As you can see, there is no reference to the INLINED constant. Instead, a ldc
(load constant) machine instruction is used to push the value of a string constant
onto the operand stack, which is subsequently stored in the local variable array.
The equivalent operation for the NOT_INLINED field requires a getstatic
machine instruction. I refer to fields such as INLINED as inlined constants.
The specification for inlined constants is somewhat buried in 13.1 The Form of a
Binary in the JLS:
References to fields that are final and initialized with compile-time
constant expressions are resolved at compile time to the constant
value that is denoted. No reference to such a constant field should be
present in the code in a binary file (except in the class or interface con-
taining the constant field, which will have code to initialize it)… 38

As can be seen in the examples in 1.4.3 Inlined Constants Always Appear to


Have Been Initialized, the parenthetical note is questionable in the case of inlined
class variables.

90 JAVA RULES
The only concrete explanation for inlined constants in the JLS is switch
statements. It is imperative for the machine instructions used to implement a
switch statement that no two case constants are the same. The compiler
must therefore check for duplicate case constants. Those constant expressions
are then inlined as a means of making sure they cannot possibly change. That is
not the only reason for inlined constants, however. The inlining of String type
constants is an important part of the intern mechanism discussed in 5.11.3 The
Intern Mechanism in Volume 1.
More generally, inlined constants in and of themselves make the Java pro-
gramming language much faster. In fact, it can be rightly said that inlined con-
stants are one of the single-most important performance optimizations in the
whole of Java technology. They eliminate an untold number of getfield and
getstatic machine instructions. An extreme example of this is a group of
related constants (all of which are inlined) declared either as a class or interface.
Such a class or interface is never loaded into a JVM. It is only used by the com-
piler. For example,

class Test {
public static void main(String[] args) {
System.out.println(InlinedConstants.s);
}
}
interface InlinedConstants {
String s = "testing, testing, testing";
}

If this program is compiled and executed using the -verbose option (which
lists the names of all the class and interface types loaded), the name of the
InlinedConstants interface cannot be found anywhere in the output

38. Gosling et al., The Java Language Specification, §13.1, “The Form of a Binary.” Note that
§3.4.8, “final Fields and Constants” is often incorrectly cited as the source of this specification.
That section only discusses the problems of binary compatibility that arise as the result of this spec-
ification for inlining constants (what this chapter refers to as the problem of changeable inlined
constants). In fact, as of the Second Edition, §3.4.8, “final Fields and Constants” still incorrectly
suggests that inlined constants must be declared static. That has never been the case. As far
back as the 1.0 release final instance variables initialized with a compile-time constant have
been inlined.

FIELDS AND METHODS 91


because it is not loaded. The sun.misc.ProxyGenerator class includes
some interesting comments in this regard:

/*
* Note that this class imports sun.tools.java.RuntimeConstants and
* references many final static primitive fields of that interface.
* By JLS section 13.4.8, the compiler should inline all of these
* references, so their presence should not require the loading of
* RuntimeConstants at runtime when ProxyGenerator is linked. This
* non-requirement is important because ProxyGenerator is intended
* to be bundled with the JRE, but classes in the sun.tools
* hierarchy, such as RuntimeConstants, are not.
*
* The Java compiler does add a CONSTANT_Class entry in the constant
* pool of this class for "sun/tools/java/RuntimeConstants". The
* evaluation of bugid 4162387 seems to imply that this is for the
* compiler's implementation of the "-Xdepend" option. This
* CONSTANT_Class entry may, however, confuse tools which use such
* entries to compute runtime class dependencies or virtual machine
* implementations which use them to effect eager class resolution.
*/39

This package-private class is used to generate a dynamic proxy when the


getProxyClass(ClassLoader loader, Class[] interfaces)
factory method in the Proxy class of java.lang.reflect is invoked.
While on the subject, there is a common misconception that all interface con-
stants are inlined. This simply is not true. For example,

interface Test {
long TIME_LOADED = System.currentTimeMillis();
}

The TIME_LOADED field is not an inlined constant because of the method invo-
cation expression in the variable initializer. The implication is that a JVM imple-
mentation has per-interface tables in which the value of interface constants are
stored.

39. Comments from the sun.misc.ProxyGenerator class.

92 JAVA RULES
Changing the value of an inlined constant breaks compatibility with pre-exist-
ing binaries.40 This is sometimes referred to as the problem of inconstant
constants, and is discussed in the following subsection.
As with compile-time constant expressions, inlined constants suffer from a
number of documentation problems, the most significant of which is what to call
them. The term inlined constant is mine. In the original JLS, these were called
primitive constants:
We call a field that is static, final, and initialized with a compile-
time constant expression a primitive constant.41

Bracha understandably deleted this sentence from the Second Edition. I say
“understandably” because the term primitive constant suggests only a primi-
tive data type declared final. In other places, where the term primitive con-
stant was used in the First Edition, it was replaced by compile-time constant
or compile-time constant field.42 There is no official explanation for this
change, but the following sentence from the evaluation of Bug Id 4015781
makes it clear that primitive constant fell out of favour.
JLS 13.4.8 has been amended. The distinction between primitive con-
stants and other constants has been abandoned.43

The term should have been replaced (by something more meaningful than com-
pile-time constant or compile-time constant field both of which are practi-
cally indistinquishable from compile-time constant expressions), however,
rather than abandoning a meaningful distinction between constants that are

40. I really do not like the term pre-existing binaries. What is the difference between an existing
binary and a pre-existing binary? You can actually find pre-exist in a couple of dictionaries, but in
reference to a change in a Java program (which we assume to be happening in the moment), all
binaries are pre-existing (except perhaps a helper class). Note also that binary (or binaries) is
just a fancy term for another class file. So to say that a “change is not compatible with pre-existing
binaries” means that the change you are contemplating or have just made is not compatible with
existing class files. Generally speaking this means that a LinkageError will be thrown, but the
definition of incompatible is sometimes extended to include other changes such as changing the
value of an inlined constant.
41. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
§13.4.8, “final Fields and Constants.” (Do not update.)
42. For example, see §13.4.8, “final Fields and Constants” in the Second Edition.
43. Evaluation of Bug Id 4015781.

FIELDS AND METHODS 93


inlined and those that are not. Indeed, the “Clarifications and Amendments to
The Java Language Specification, Second Edition” document includes the fol-
lowing amendment to 4.5.4 final Variables.
We call a variable that is final, of primitive type or type String,
and initialized with a compile-time constant expression a constant
variable.44 [emphasis added]

This change is encouraging because it acknowledges the need for a term, but
the choice of constant variable is as unfortunate as primitive constant. Why
not call a thing what it is? In this case, that would be an inlined constant.
In the original JLS, the following statements from §15.27, “Constant Expres-
sion” and §13.1, “The Form of a Java Binary” were inconsistent.
A compile-time constant expression is an expression denoting a value
of primitive type or a String that is composed using only the follow-
ing:

• Simple names that refer to final variables whose initializ-


ers are constant expressions 45 [emphasis added]
References to fields that are static, final, and initialized with
compile-time constant expressions are resolved at compile time to the
constant value that is denoted.46 [emphasis added]

The first quote says final and the second one says static final. There is
a discussion of this in Bug Id 4262182. Inlined constants do not have to be
declared static, and the Second Edition fixed 13.1 The Form of a Java Binary
by dropping the static keyword. It did not, however, make the same change
in 13.4.8 final Fields and Constants.
The best way to avoid problems with “inconstant constants” in widely-
distributed code is to declare as compile time constants only values

44. Unascribed, “Clarifications and Amendments to The Java Language Specification, Second Edi-
tion” available online at java.sun.com/docs/books/jls/clarifications-2-2nd-
ed.html, (Mountain View: Sun Microsystems, 1995-2003), “JLS 4.5.4.”
45. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
§15.28, “Constant Expression.” (Do not update.)
46. Ibid., “The Form of a Java Binary.” (Do not update.)

94 JAVA RULES
which truly are unlikely ever to change. …Other than for true mathemat-
ical constants, we recommend that source code make very sparing
use of class variables that are declared static and final.47

13.1 The Form of a Java Binary is comparatively buried in the specification, so I


am inclined to think it is going to take some time for word to get around that non-
static fields are also inlined. That is not actually a change, however. As noted
in Bug Id 4262182, non-static constants have been inlined since at least the
1.2 release. Bug Id 4015781 indicates that this has always been the behavior,
all the way back to the 1.0 release.

1.5.2.1 The Problem of Changeable Inlined Constants


This section is the Mastering The Fundamentals analogue to 13.4.8 final
Fields and Constants in the JLS, which discusses the problem of inconstant
constants. I mention this in part so that you understand the terminology used in
this section originates in the JLS. The term inconstant constant suffers from
the same problem as constant expression in that it is all too easily confused
with the more common definition of constants as any variable declared final.
In the case of constant expressions, the problem is solved by using the more for-
mal term compile-time constant expression. I tried expanding the term incon-
stant constant to inconstant inlined constants, but that is going from cute to
ridiculous. The term changeable inlined constants is much more descriptive.
Changeable inlined constants are a subtle problem in the Java programming lan-
guage. Understanding the problem is not difficult. The solution is not that difficult
to understand either. The only difficulty is just remembering to implement what
you already know about the problem of changeable inlined constants when add-
ing the final keyword to the declaration of a field that is either a primitive data
type or String.
Changing the value of a inlined constant always breaks compatibility with pre-
existing binaries. As explained in the JLS:
If a field is a compile-time constant, then deleting the keyword final
or changing its value will not break compatibility with pre-existing bina-

47. Gosling et al., §13.4.8, “final Fields and Constants.”

FIELDS AND METHODS 95


ries by causing them not to run, but they will not see any new value for
the constant unless they are recompiled.48

For example,

public class ExistingBinary {


public static void main(String[] args) {
System.out.println(100 / InlinedConstant.x);
}
}

Now consider the following class in a different compilation unit.

public class InlinedConstant {


static final int x = 4;
}

Executing the ExistingBinary program prints 25. The following change to


InlinedConstant is not compatible with ExistingBinary.

class InlinedConstant {
static final int x = 2;
}

You would expect ExistingBinary to print 50, but instead it continues to


print 25 until recompiled because the value of x is inlined.

Existing binaries (read other class files) do not see the change in an
inlined constant until recompiled.

That is the whole problem in a nutshell. What is the solution? The JLS suggests:
The best way to avoid problems with “inconstant constants” in widely-
distributed code is to declare as compile time constants only values
which truly are unlikely ever to change. Many compile time constants in
interfaces are small integer values replacing enumerated types, which
the language does not support; these small values can be chosen arbi-
trarily, and should not need to be changed. Other than for true mathe-

48. Gosling et al., §13.4.8, “final Fields and Constants.”

96 JAVA RULES
matical constants, we recommend that source code make very sparing
use of class variables that are declared static and final.49

The first very important point to add to this suggestion is that the problem of
changeable inlined constants does not apply to reference types other than
String. Note also that inlined constants should be class variables because it
makes no sense to have copies of a variable that is truly constant in every
instance of a class. Nevertheless, this advice applies equally to instance vari-
ables (or non-static fields). In other words, the last sentence of this quote
should read:
Other than for true mathematical constants, we recommend that Java
code make very sparing use of primitive data type or String type
fields declared final .

In declaring such a field final you expose yourself to the problem of change-
able inlined constants.

When adding the final keyword to the declaration of a field


that is either a primitive data type or String ask yourself: “Is
the value of this field ever going to change?”

As stated above, the hard part is remembering to ask yourself this question.
When declaring an interface, you should be especially alert to the problem in
changeable inlined constants because the fields in an interface are implicitly
final. That makes interfaces especially susceptible to the problem of change-
able inlined constants.
When are such fields likely to never change? Asking this question is the same
as asking when is it okay to use inlined constants. The following list is doubtless
incomplete, but covers most uses of inlined constants of which I am aware:
• True mathematical constants
• Bit masks
• Mnemonics

49. Gosling et al., §13.4.8, “final Fields and Constants.”

FIELDS AND METHODS 97


• Property names and other hash table keys
• Convenience constants
The most obvious of these uses are true mathematical constants such as
Math.PI , Integer.MAX_VALUE, Character.MIN_RADIX, etc. Bit
masks are discussed in 4.7 A Bitwise Primer. To understand mnemonics, you must
recall that literals are unnamed constants. Variables declared final are
named constants. Sometimes a literal is assigned to a static final field for
no other reason than to give it a name. Named constants are preferable to
unnamed constants when the literal value is used more than once or twice. In the
case of a String type literal, using a named constant also helps to avoid the
problem of misspelling. Here is a example from JarFile in the
java.util.jar package:

public static final String MANIFEST_NAME =


"META-INF/MANIFEST.MF";

Mnemonics may also be numeric. For example,

private static final int ONE_MINUTE = 60*1000;


private static final int ONE_HOUR = 60*ONE_MINUTE;
private static final int ONE_DAY = 24*ONE_HOUR;

These are from the java.util.TimeZone class. There is a fine line between
convenience constants and mnemonics. The easiest way to distinquish between
the two is that mnemonics are not enumerated constants. By that I mean there
is usually only one of them. For example, consider the following int type con-
stants from an old version of a package-private class in the java.net package:

private static final int REQUEST_GRANTED = 90;


private static final int REQUEST_REJECTED = 91;
private static final int REQUEST_REJECTED_NO_IDENTD = 92;
private static final int REQUEST_REJECTED_DIFF_IDENTS = 93;

public static final String socksDefaultPortStr = "1080";

The socksDefaultPortStr field is clearly a mnemonic because there is


only one. The other fields are response codes. Although there is only four of
them, this is clearly an enumerated type (declared as convenience constants).

98 JAVA RULES
The use of inlined constants for true mathematical constants, bit masks, and
mnemonics will always be a part of the Java programming language. The same
is true of using strings (as property names) to access a Properties object
because of the following specification.
Because Properties inherits from Hashtable, the put and
putAll methods can be applied to a Properties object. Their use
is strongly discouraged as they allow the caller to insert entries whose
keys or values are not Strings. The setProperty method should
be used instead. If the store or save method is called on a “compro-
mised” Properties object that contains a non-String key or value, the
call will fail.50

The use of inlined constants (both int and String type) as keys for hash
tables other than Properties objects are actually just one of the many uses
of convenience constants. Convenience constants were originally suggested as
a replacement for “enums” (enumerated constants declared using the enum
keyword in the C and C++ programming languages) by no less than Dr. Gosling.
They have fallen out of favour, however, and are gradually being replaced by the
typesafe enum pattern discussed in 1.5.5 Enumerated Types.
What about primitive data types or String type fields that are not true
mathematical constants, bit masks, mnemonics, or property names? Is there a
hard and fast rule that such fields should not be declared final? Generally
speaking the answer is Yes. There are always exceptions, however. Consider
the following system-wide values in the java.io.File class.

public static final char separatorChar = fs.getSeparator();


public static final String separator = "" + separatorChar;
public static final char pathSeparatorChar =
fs.getPathSeparator();
public static final String pathSeparator = "" +
pathSeparatorChar;

Note that these fields fail to heed the advice of the JLS not to use final for
read-only access. They are not inlined constants (which would be disastrous),
however, because all four variable initializers include either a variable or a

50. API docs for the java.util.Properties class.

FIELDS AND METHODS 99


method invocation expression. If for whatever reason you are going to make an
exception to the general rule that primitive data types and String type fields
should not be declared final unless they are true mathematical constants, bit
masks, mnemonics, or property names, you must make sure that the value is
either inaccessible or not inlined.
The first line of defense against the problem of changeable inlined constants
is to declare them private. Merely by declaring the field private you have
solved the problem of changeable inlined constants breaking compatibility with
pre-existing binaries. In addition, declaring fields private is basic to encapsu-
lation. In fact, it could be argued that the problem of changeable inlined con-
stants is a good example of why you should encapsulate the state of an object.
Failing that, you have to use a workaround to make sure the value of a primitive
data type or String type field is not inlined. In short, you have to defeat the
design of inlined constants. The easiest workarounds are to use a variable,
method invocation expression, or class instance creation expression in the vari-
able initializer (as does the File class). The use of any one of these is by defini-
tion not a compile-time constant expression. Therefore the value the expression
will not be inlined. For example,

static final char c = new Character('a').charValue();


static final boolean b = Boolean.TRUE.booleanValue();
static final int i = new Integer(0).intValue();
static final long l = new Long(0).longValue();
static final float f = new Float(0).floatValue();
static final double d = new Double(0).doubleValue();
static final String s = new String("not inlined");

None of these values are inlined. Unless you were going to use a variable,
method invocation expression, or class instance creation expression to initialize
the field anyway, a blank final is more efficient than any of these concoctions.
The blank final can be initialized in an initialization block that immediately follows
the declaration. Blank finals are never inlined.

100 JAVA RULES


1.5.3 Declaring Mutable Objects final
Other than String (because of the problem of changeable inlined constants),
declaring immutable objects final is never a problem. Declaring mutable
objects final, however, is at least as problematic as inlined constants
because doing so does not prevent someone from using the reference to invoke
a mutator method that changes the state of the mutable object. If you truly do
not want the value of a reference type (including the state of the object refer-
enced) to be changed by other classes, the field must be declared private.
Furthermore, you must make defensive copies as discussed in 1.6.3.3 Making
Defensive Copies.
This means the motivation for declaring most reference types (those that are
mutable) final is different from declaring primitive data types and immutable
objects final. The latter are declared final so that the value does not
change, whereas references to immutable objects are declared final so that
the reference itself does not change. That is a fundamentally different rea-
son for using the final modifier. Furthermore, it implies the following.

The implication of declaring references to mutable object final so


that the reference itself does not change is that the field is accessible.

In effect, you are allowing a client programmer direct access to a component


interface. Here are some very well-known examples:

public final static InputStream in;


public final static PrintStream out;
public final static PrintStream err;

These are the standard I/O fields System.in, System.out, and


System.err. Here we have a very good example of why using accessor meth-
ods is not always the answer. Were the standard I/O streams in, out, and err
only accessible through accessor methods, System.out.println would
become even more cumbersome. Here is one possibility:

System.getStdIn().println("Hello World!");

FIELDS AND METHODS 101


As you can see, this is an interface design question, one that actually favors the
use of public fields over private fields and public accessor methods. In
this case, the fully overloaded print and println methods are the “compo-
nent interface” that client programmers directly access.
In utility classes such as System, public static final fields that ref-
erence mutable objects are somewhat common. They are also an integral part
of the Façade design pattern. The raison d'être for the Façade design pattern is
to allow access to component interfaces. Those components are usually refer-
ences to mutable objects. They are declared public to allow direct access to
their interfaces. That does not mean, however, that client programmers should
be allowed to substitute a different component or inadvertently assign a null
reference to one of the fields. Therefore, they must be declared final. Other
than utility classes and the Façade design pattern, I would expect most refer-
ences to mutable objects to be declared private. If declared private,
there is no reason to use the final modifier.

1.5.4 Blank Finals


A blank final is a variable declared final that does not have a variable initial-
izer.51 A blank final must be initialized before it is used. After the initial assign-
ment, the value of a blank final (like that of any other constant) cannot be
changed.
Blank finals were introduced to the Java programming language in the 1.1
release as part of the Inner Classes Specification. Before blank finals there
was a rule that a variable declared final had to have a variable initializer:
A field can be declared final, in which case its declarator must
include a variable initializer or a compile-time error occurs.52

This specification was always a problem because the raison d'être for initializa-
tion block (complex initialization logic and invoking methods that throw checked

51. I normally use a fixed font for keywords, but make an exception for the term blank final. The
problem is that I never pluralize words in fixed font (such as type names), and more often than not
the term blank final is used in the plural.
52. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
§8.3.1.2, “final Fields.” (Do not update.)

102 JAVA RULES


exceptions) is no different for constants as it is for other fields. As stated in the
“Changes for Java 1.1” document:
Deferred initialization can be useful when the proper value can only be
calculated by a loop or other code that is hard or impossible to encode
in a variable initializer, such as code that throws exceptions that must
be caught and handled.53

Assignments to blank finals are still much more restrictive than they are for non-
final fields. For example, this quote says “calculated by a loop” (i.e., com-
plex initialization logic), not assigned in a loop. Attempting to assign a value to a
blank final anywhere in a loop generates a compiler error. (There is an example
of this below.) Moreover, fields that are blank finals must be definitely
assigned somewhere in the special initialization method that initializes
the corresponding class or object. For static fields, that means the defi-
nite assignment must occur somewhere in a static initialization block. For
non-static fields, the definite assignment must occur either in an instance ini-
tialization block or every constructor. A compiler error is generated if one or
more of the constructors do not assign a value to the blank final. For example,

class Test {
final String s;
Test() { }
Test(String s) { this.s = s; }
}

Attempting to compile this program generates the following compiler error:

Test.java:3: variable s might not have been initialized


Test() { }
^
1 error

If blank finals are initialized in constructors, those constructors cannot be


chained together. The effect of doing so would be what is sometimes called the
double-assignment of a blank final.54 For example,

53. Ken Arnold and James Gosling, “Appendix D, Changes for 1.1” (excerpted from the fourth print-
ing of The Java Programming Language, (Boston, Addison-Wesley Professional, 1996), D.1.2,
“New Uses for final.”

FIELDS AND METHODS 103


public class Test {
final int blankFinal;
Test() { blankFinal = 0; }
Test(int x) {
this();
blankFinal = x;
}
}

Blank finals cannot be initialized inside of loops for the same reason. For example,

public class Test {


public static void main(String[] args) {
final int blankFinal;
for (int i=0; i < 10 ; i++) {
blankFinal = i;
System.out.println("blankFinal = " + blankFinal);
}
}
}

Attempting to compile this class generates the following compiler error:

Test.java:4: variable blankFinal might be assigned in loop


for (int i=0; i < 10 ; i++) {
^
1 error

That this is the same tentative language used in definite assignment compiler
errors is no coincidence. The rules of definite assignment were extended in the
1.1 release to include fields that are blanks finals as well as local variables. Thus
Chapter 16, “Definite Assignment” in the JLS begins as follows.
Each local variable and every blank final field must have a defi-
nitely assigned value when any access of its value occurs. A Java
compiler must carry out a specific conservative flow analysis to make
sure that, for every access of a local variable or blank final field f, f
is definitely assigned before the access; otherwise a compile-time error
must occur.55

54. The term double-assignment is from the “Compatibility with Previous Releases” document
cited above. The examples of double-assignment in this section are based on examples in that doc-
ument.

104 JAVA RULES


As explained in 1.8.1 Definite Assignment, the tentative language allows for the
possibility that the “conservative” analysis performed by the compiler may be at
odds with what a programmer knows to be true.
Local variables and parameters can also be declared final. As explained
in 1.5.6 Declaring Local Variables and Parameters final, the primary reason
for doing so is so that they can be used in block classes (local and anonymous
classes). If a local variable declared final so that it can be used in a block
class is a blank final, definite assignment must occur before the declara-
tion of the block class. I place emphasis on declaration because if you under-
stand that the value of the local variable is passed as an argument in the class
instance creation expression, you may tend to think the placement of a block
class declaration is unimportant. For example,

class Test {
void print() {
final char EXCLAMATION_MARK;
class LocalClass {String s = "Hello World" +
EXCLAMATION_MARK;}
EXCLAMATION_MARK = '!';
System.out.println(new LocalClass().s);
}
}

Attempting to compile this class generates the following compiler error:

Test.java:5: variable EXCLAMATION_MARK might not have been


initialized
EXCLAMATION_MARK;}
^
1 error

There are other special rules for initializing blank finals. These are just some of
the more obvious ones.

55. Gosling et al., introduction to Chapter 16, “Definite Assignment.”

FIELDS AND METHODS 105


SPECIAL LAST MINUTE NOTE: THE FOLLOWING SEC-
TION ON ENUMERATED TYPES IS IN THE PROCESS OF
BEING COMPLETELY REWRITTEN FOR THE 1.5 “TIGER”
RELEASE. NOTE ALSO THAT IT ADVOCATES THE USE OF
INTERFACES TO DECLARE CONSTANTS. THAT WAS A MIS-
TAKE IN LIGHT OF THE DISCUSSION IN 1.5.7 The Constant
Interface Antipattern.

NOTE 1.2
If some programmers are willing to sacrifice everything at the alter of
performance (including readability), the bitwise operators are the gods
to which they pray. This religious order in which every bit is cherished
has existed undisturbed since the dawn of computer time. Enter the
typesafe enum pattern. If taken to the extreme (read “full-featured ab-
stractions,”56 far beyond mere compile-time type checking), the type-
safe enum pattern is a vain attempt to preempt a world in which
the thought of exchanging a bit mask for an object is nothing
less than ludicrous. This is harsh language, I realize, but the point
must be made that this cannot be an either or choice. The bit worship-
ers must realize that the opportunity to eliminate an entire class of runt-
ime exceptions is an efficiency of a different sort; and the typesafe
enum pattern enthusiasts must realize that they are standing on holy
ground. Is a marriage of these two worlds possible? Read on.

1.5.5 Enumerated Types


The declaration of an enumerated type specifies the “set of values” that can
be stored in that type.57 The individual values are referred to as enumerated

56. Joshua Bloch, Effective Java Programming Language Guide, (Boston: Addison-Wesley,
2001), “Item 21: Replace enum constructs with classes.”
57. This is sometimes referred to as a closed set or a bounded domain by the mathematically liter-
ate.

106 JAVA RULES


constants. Enumerated types are supported at the language level in the C and
C++ programming languages. Dr. Gosling elected not to do so in the Java pro-
gramming language. His explanation for this decision was given in 2.2 Features
Removed from C and C++ in “The Java Programming Language Environment, A
White Paper” dated May, 1996, and is quoted here in its entirety (largely
because of its historical significance):
No Enums

Java has no enum types. You can obtain something similar to enum by
declaring a class whose only raison d'etre is to hold constants. You
could use this feature something like this:

class Direction extends Object {


public static final int North = 1;
public static final int South = 2;
public static final int East = 3;
public static final int West = 4;
}

You can now refer to, say, the South constant using the notation
Direction.South.
Using classes to contain constants in this way provides a major advan-
tage over C's enum types. In C (and C++), names defined in enums
must be unique: if you have an enum called HotColors containing
names Red and Yellow, you can't use those names in any other
enum. You couldn't, for instance, define another Enum [sic] called
TrafficLightColors also containing Red and Yellow.
Using the class-to-contain-constants technique in Java, you can use the
same names in different classes, because those names are qualified by
the name of the containing class. From our example just above, you
might wish to create another class called CompassRose:

class CompassRose extends Object {


public static final int North = 1;
public static final int NorthEast = 2;
public static final int East = 3;
public static final int SouthEast = 4;
public static final int South = 5;
public static final int SouthWest = 6;

FIELDS AND METHODS 107


public static final int West = 7;
public static final int NorthWest = 8;
}

There is no ambiguity because the name of the containing class acts as


a qualifier for the constants. In the second example, you would use the
notation CompassRose.NorthWest to access the corresponding
value. Java effectively provides you the concept of qualified enums, all
within the existing class mechanisms.58

Thus began one of the longest running design discussions in the whole of the
Java programming language. This focus on the namespace problems in C and
C++ was something of a ruse. In a more casual setting, Dr. Gosling later admit-
ted that he more or less ran out of time in trying to “converge on a design that
made sense” as a replacement for enum in an object-oriented programming lan-
guage:
Enumerations were left out of the Java spec not because I think they're
a bad idea, but because I couldn't converge on a design that made
sense. Enumerations means different things to different people.

There was more than enough grayness in the area that I decided to put
the issue aside for the time being. 59

In fact, enum was apparently a reserved word while the 1.0 release of the JDK
was still in beta testing.
The kind of enumerated constants advocated in this white paper are used
extensively in pre-1.4 releases of the core API, and have come to be known as
convenience constants (or less commonly as the int enum pattern). The fol-
lowing example of date and time fields is from the Calendar class.

public final static int ERA = 0;


public final static int YEAR = 1;

58. James Gosling and Henry McGilton, “The Java programming Language Environment, A White
Paper” (Mountain View: Sun Microsystems, 1996), java.sun.com/docs/white/langenv/
index.html.
59. Gosling, Letters to the Editor, Java World, June 1998, www.javaworld.com/javaworld/
jw-06-1998/jw-06-letters.html.

108 JAVA RULES


public final static int MONTH = 2;
public final static int WEEK_OF_YEAR = 3;
public final static int WEEK_OF_MONTH = 4;
public final static int DATE = 5;
public final static int DAY_OF_MONTH = 5;
public final static int DAY_OF_YEAR = 6;
public final static int DAY_OF_WEEK = 7;
public final static int DAY_OF_WEEK_IN_MONTH = 8;
public final static int AM_PM = 9;
public final static int HOUR = 10;
public final static int HOUR_OF_DAY = 11;
public final static int MINUTE = 12;
public final static int SECOND = 13;
public final static int MILLISECOND = 14;
public final static int ZONE_OFFSET = 15;
public final static int DST_OFFSET = 16;

As you can see, convenience constants are usually public static final
int declarations, but String is sometimes used as the data type. The follow-
ing example of the less common String type convenience constants is from
the BorderLayout class in the java.awt package.

public static final String NORTH = "North";


public static final String SOUTH = "South";
public static final String EAST = "East";
public static final String WEST = "West";
public static final String CENTER = "Center";

Though all of these examples are of public fields, private convenience


constants are not at all uncommon.
Convenience constants are a serious problem in the Java programming lan-
guage because they represent data types that are not susceptible to the com-
pile-time type checks discussed in 5.3 Java is a Strongly Typed Language. For
example, the expectation in the Calendar class is that client programmers will
use one of the above convenience constants as the first argument when invoking
the set(int field, int value) method. Those convenience constants
represent an enumerated type in which the “set of values” are zero through six-
teen. The problem is that there is no corresponding type name to use in the
method declaration. The compiler is told only that the data type is an int.

FIELDS AND METHODS 109


Therefore any value from zero to 2147483647 can be passed. And what hap-
pens if a value not in the range of 0-16 is passed to the set(int field,
int value) method in the Calendar class? The answer is that an Array-
IndexOutOfBoundsException is thrown. A comparable implementation
using the typesafe enum pattern (discussed below) would have caught this prob-
lem at compile-time.
The ArrayIndexOutOfBoundsException thrown by the set(int
field, int value) method in the Calendar class is a system-induced
argument check. Usually the argument checks for convenience constants must
be explicitly coded. For example, the SwingConstants interface in the
javax.swing package defines an enumerated type that has two values,
HORIZONTAL and VERTICAL. Here is an example of a private method in
the core API that is used to check the value passed at runtime:

private void checkOrientation(int orientation) {


switch (orientation) {
case VERTICAL:
case HORIZONTAL:
break;
default:
throw new IllegalArgumentException(
"orientation must be one of: VERTICAL, HORIZONTAL");
}
}

Most optimized JVM will inline this method. It is invoked from several different
public methods so that the switch statement doesn’t clutter those meth-
ods. The use of a typesafe enum obviates the need for such argument checks.
The other problems with convenience constants pale in comparison to throw-
ing runtime exceptions versus compile-time type checking:
• Client programmers sometimes ignore convenience constants and use an
int or String literal instead. That is a problem because it introduces the
possibility that an incorrect value is passed. The eternal debate over
whether Calendar.JANUARY should be 0 or 1 is a classic example of
this. This problem is exacerbated when using String type convenience
constants because misspellings are not caught by the compiler. It is even

110 JAVA RULES


possible to confuse the name of convenience constants such as NORTH
and TOP in the SwingConstants interface.
• The use of String type convenience constants is a performance problem
because string comparisons (which loop through two different char
arrays) are expensive.
• Convenience constants are primitive data types (at least most of them) and
therefore cannot be added to non-array containers without incurring the
expense of object wrappers.
• The toString() representation of an arbitrarily numbered int type con-
venience constant is meaningless.
• Convenience constants are inlined. As discussed in 1.5.2.1 The Problem of
Changeable Inlined Constants, this means you cannot change the value of a
convenience constant without breaking compatibility with existing binaries.
These problems with convenience constants are well documented. So why are
they used extensively in the core API? As explained by Bloch, “the only reason
that typesafe enums are not used more heavily in the Java platform APIs is that
the typesafe enum pattern was unknown when many of those APIs were writ-
ten.”60 While there are isolated examples of the typesafe enum pattern in pre-1.4
releases of the core API (such as Character.UnicodeBlock61), the type-
safe enum pattern is being used exclusively in newer packages such as
java.nio and java.util.logging. Besides compile-time type checking,
the typesafe enum pattern offers the advantage of a meaningful toString()
method, no inlining, and enumerated types that can be added to non-array con-
tainers. Basically, all of the problems associated with convenience constants are
solved by using the typesafe enum pattern discussed in the remainder of his sec-
tion.
The alternative to the convenience constants originally proposed by Dr. Gos-
ling is an object-oriented enumerated type. The idea of an object-oriented
enumerated type has been evolving over the years. The typesafe enum pat-

60. Bloch, Effective Java , “Item 21: Replace enum constructs with classes.”
61. As far as I know, this was the first serious break with convenience constants in the core API. It
was a bold initiative, and the responsible programmer(s) should be commended for setting an exam-
ple.

FIELDS AND METHODS 111


tern 62 represents a plateau of sorts on which there is general agreement on the
fundamentals, but I am certain that this discussion is not over. For example,
Bloch is the undisputed king of the typesafe enum pattern because of his
exhaustive documentation in Effective Java, but none of the examples in Effec-
tive Java includes a getValue() or intValue() method that returns an
integer value for the enumerated constant. Such methods were included in
actual implementations of the typesafe enum pattern in the 1.4 release, but they
still do not provide a means of using the values of a typesafe enum as case con-
stants in a switch statement.

The single most damning feature of the typesafe enum pattern at


present is the fact that the values of a typesafe enum cannot be
used as case constants in a switch statement.

This is the central complaint in Bug Id 4401321, which is the RFE to sup-
port enum in the Java programming language, and it is as true today as it
was on January 2, 2001 when the RFE was submitted. Bloch refers to this as a
“minor disadvantage”63 but I am not sure everyone would agree. What we are
talking about here is coding styles. The suggestion that nested if-then-else
statements should be used instead of a switch statement is sacrilege for the
bit worshipers. I am only partly kidding because Bloch really did cross the line
when suggesting that bitsets should be exchanged for a collection of objects in
the following quote.
The typesafe enum pattern has few disadvantages when compared to
the int pattern. Perhaps the only serious disadvantage is that it is
more awkward to aggregate typesafe enum constants into sets. With
int-based enums, this is traditionally done by choosing enumeration
constant values, each of which is a distinctive positive power of two,
and representing a set as the bitwise OR of the relevant constants:

62. The typesafe enum pattern is an example of the more general Flyweight design pattern in
Design Patterns .
63. Bloch, Effective Java, “Item 21: Replace enum constructs with classes.”

112 JAVA RULES


//Bit-flag variant of int enum pattern
public static final int SUIT_CLUBS = 1;
public static final int SUIT_DIAMONDS = 2;
public static final int SUIT_HEARTS = 4;
public static final int SUIT_SPADES = 8;

public static final int SUIT_BLACK = SUIT_CLUBS |


SUIT_SPADES;

Representing sets of enumerated type constants in this fashion is con-


cise and extremely fast. For sets of typesafe enum constants, you can
use a general purpose set implementation from the Collections Frame-
work, but this is neither as concise nor as fast:

Set blackSuits = new HashSet();


blackSuits.add(Suit.CLUBS);
blackSuits.add(Suit.SPADES);

While set of typesafe enum constants probably cannot be made as con-


cise or as fast as sets of int enum constants, it is possible to reduce
the disparity by providing a special-purpose Set implementation that
accepts only elements of one type and represents the set internally as
a bit vector.…64

His real faux pas, however, is obsessing with the idea of evolving convenience
constants into “full-featured abstractions,”65 My question is why? The raison
d'etre for using the typesafe enum pattern is compile-time type checking. The
idea that every enumerated type should evolve into a “full-featured abstraction”
overlooks the common case of a discrete set of integers that have nothing
whatsoever to do with behavior. The Level class is a perfect example. The
enumerated constants in the Level class are primarily used in the following if
statement to determine if a log record should be published.

64. Bloch, Effective Java , “Item 21: Replace enum constructs with classes.” I lavished so much
praise on Bloch in Volume 1 as to embarrass myself, so I trust I can be allowed the occasional con-
structive criticism for the general good. There can be no doubt, however, that he has achieved the
same status in the pantheon of Java gods as Dr. Gosling, Guy Steele, Doug Lea, and a great many
others. Besides that, I made essentially the same mistake in Volume 1 by suggesting that there
should be a HashCode class to facilitate the computation of hash codes. In both cases, there is a
profound disregard for the importance of bitwise programming (which is something I did not fully
develop until writing 4.7 A Bitwise Primer).
65. Ibid.

FIELDS AND METHODS 113


if (level.intValue() < levelValue || levelValue == offValue) {
return;
}

Level is never going to evolve into a “full fledged abstraction.” Levels are a dis-
crete set of integers and nothing else.
What can be done to make the typesafe enum pattern usable in switch
statements? Interestingly, Bloch hints at that answer in the very next sentence of
the passage quoted above:
…Such a set is best implemented in the same package as the element
type to allow access, via a package-private field or method, to a bit
value internally associated with each typesafe enum constant.66

If the field were public instead of package-private, it could be used as a case


constant in switch statements. Here is an example from 6.10.1.2 The
UncaughtException Class:

public final class Redirect {


private final String name;
private final int value;
private Redirect(int value, String name) {
this.value = value;
this.name = name;
}
public int intValue() { return value; }
public String toString() { return name; }

//pass values (emphasize type safety)


public static final Redirect NEITHER =
new Redirect(Constants.NEITHER, "Neither");
public static final Redirect STANDARD_OUTPUT =
new Redirect(Constants.STANDARD_OUTPUT, "Standard output");
public static final Redirect STANDARD_ERROR =
new Redirect(Constants.STANDARD_ERROR, "Standard error");
public static final Redirect BOTH =
new Redirect(Constants.BOTH, "Both");

66. Ibid., Note that this explains why none of the examples in Effective Java include a
getValue() or intValue() method that returns an integer value for the enumerated con-
stant.

114 JAVA RULES


//comparison values (emphasize speed and convenience)
public interface Constants {
int NEITHER = 0x01;
int STANDARD_OUTPUT = 0x02;
int STANDARD_ERROR = 0x04;
int BOTH = 0x08;
}
}

This is an example of a simple typesafe enum. It is referred to as “simple” pri-


marily because it is not serializable. While even more verbose as existing type-
safe enums, it is still a tidy solution in that the implementation is in a single class.
A top-level interface need not be used for the “comparison values.” A nested
interface works just as well, requiring only the client programmers type a slightly
longer fully qualified name when either importing or implementing the interface.
In this case, clients must type either import Redirect.Constants or
implements Redirect.Constants. The name of the interface does not
matter for the same reason. In fact, Constants is a good programmer con-
vention.
Here is an example of a method that uses this typesafe enum in a switch
statement:

public synchronized void init(JFrame frame,


Logger logger,
Redirect redirect) {
//a null frame is okay
if (logger == null)
throw new IllegalArgumentException("logger is null");
if (redirect == null)
throw new IllegalArgumentException("redirect is null");

switch(redirect.intValue()) {
case NEITHER:
break;
case STANDARD_OUTPUT:
System.setOut(sysOut);
break;
case STANDARD_ERROR:
System.setErr(sysErr);
break;

FIELDS AND METHODS 115


case BOTH:
System.setOut(sysOut);
System.setErr(sysErr);
default:
assert false;
}

The typesafe enum redirect is always available should you want to use it as
a hash table key or for some other reason. The point is that by providing an
int value that can be used in bitwise programming you offer client pro-
grammers the greatest flexibility in deciding how to use the typesafe
enum. Dyed-in-the-wool bitwise programmers will mostly likely choose to com-
pletely ignore the typesafe enum after invoking redirect.intValue(),
and they should have that option.
As further support for bitwise programming, enumerated constants that
are part of a public interface should be initialized with a power of two.
There are 32 powers of two (or bit masks) in an int, which more than exceeds
the number of enumerated constants in most enumerated types. If more enu-
merated constants are needed, a long can be used.
The interface contract for enumerated types should specifically state that
the int value of an enumerated constant will never change. Once the
immutable class modifier is implemented (as I am utterly convinced will hap-
pen for a number of reasons), the language will guarantee this.
The typesafe enum pattern reduces but does not entirely eliminate the possi-
bility of a runtime exception being thrown. Compile-time type checking assures
only that one of the enumerated constants can be passed, but there is now the
possibility that a null reference is passed. The example above uses an explicit
argument check. Other methods may use a system-induced argument check
that throws a NullPointerException. I would argue that passing null is
an IllegalArgumentException, but other programmers may feel just as
strongly that NullPointerException should always be thrown. This must
be regarded as a matter of style because both are runtime exceptions.
I arrived at this implementation of the typesafe enum pattern by reasoning
that typesafe enums should support the use of switch statements (or more

116 JAVA RULES


generally the coding style of bitwise programmers), and then reasoning as fol-
lows.
1. The client programmer should not be made to declare local variables that
correspond to the value of the typesafe enums passed because that
requires looking at the source code which may not be available. Doing so is
also a maintenance problem.
2. Nor can the client programmer invoke intValue() to initialize local vari-
ables with the value of the corresponding typesafe enum because the result-
ing variable initializer is not a compile-time constant expression which
means the local variables cannot be used as case constants.
3. Therefore the int type values must be declared in the same class or inter-
face as the typesafe enum
4. Now you face the problem of having two sets of fields. One int type used
in comparisons and the other a typesafe enum used when passing values.
Any design the requires different names to differentiate these two sets of
value is going to be ugly, so the design must use a nested type.
5. The class of the typesafe enum is used as a parameter type and so should
be the easiest to read. The interface type name is only used in import or
implements clauses. Therefore the outermost type should be a class
type that declares the typesafe enum. A nested interface is used to declare
the int type comparison values.
6. To assure the integrity of the design, the int values passed to the type-
safe enum constructors should be the interface constants. Note that when
doing so the interface constant names must be qualified with the interface
name (which should always be Constants) or else the compiler will gener-
ate an illegal forward reference compiler error.
7. Such a design can be nested, but it is easier to use if declared as a package
member. The use of a helper class is out of the question because such a
class cannot be declared public.
The problem with the addition of a nested interface is that it makes the typesafe
enum pattern even more verbose. This has become the central issue with
detractors of the typesafe enum pattern, as can be see in the following evalua-
tion of Bug Id 4401321.

FIELDS AND METHODS 117


...While we are great fans of the “Typesafe Enum” pattern describe [sic]
in Item 21 of Bloch's “Effective Java” (Addison-Wesley, 2001), we
understand that this pattern's verbosity is a real disadvantage. We are
actively investigating a language feature that would provide all the
advantages of this pattern while eliminating the verbosity.

That language feature may be the implementation of an EnumeratedType


baseclass in the core API as requested in RFE 4403347,67 implementation of
the immutable class modifier, or both. I believe that it will be some combina-
tion of both that advances enumerated types way beyond their current imple-
mentation in any programming language.
The remainder of this section discusses the idea of an EnumeratedType
baseclass in the java.util package. There are already two such base-
classes in the core API. Both were introduced in the 1.4 release. One is
EnumSyntax in the javax.print.attribute package. The other is
Level in the java.util.logging package. The latter is both serializable
and internationalized and as such is the very definition of a complex typesafe
enum. At the point at which you are having to declare a readResolve()
method to make the typesafe enum pattern work, however, the simplicity of con-
venience constants (or the int type enum pattern) becomes very appealing.

Given the extent to which convenience constants are used in pre-1.4


releases of the core API, there is a design imperative to make the
declaration of complex typesafe enums as easy as convenience con-
stants.

This is precisely the point that Vladimir Roubtsov makes in a recent contribution
to the enumerated type design discussion.68 I could not agree with him more,

67. As of this writing RFE 4403347 has only nine votes (one of which is mine). I think this is mislead-
ing, however. I bet that a significant number of the 466 votes (as of this writing) for RFE 4401321
would be changed to 4403347 were there not 100+ pages of comments (making it easy to forget
which RFE is under discussion) at the bottom of the former. In fact, there are so many proposals for
an Enum or EnumeratedType baseclass in those comments that RFE 4403347 actually refer-
ences it as a source of examples.
68. Vladimir Roubtsov, “Java Tip 122: Beware of Java typesafe enumerations,” (New York, Java-
World, 2002), www.javaworld.com/javaworld/javatips/jw-javatip122.html

118 JAVA RULES


but the solution to this problem is not to revert to convenience constants. The
solution is to implement Serializable in a baseclass that can be extended
by all enumerated types. Level makes a pretty good case for such a base-
class. It has way too much “boilerplate” as Bloch would say, —more than 100
lines of code (355 lines if you includes blank lines and comments) to declare
seven enumerated constants. This should not be construed as a criticism of
the implementation, however. A baseclass for typesafe enums really should be
internationalized (as is the entire logging facility), and the readObject()
method in Level is the most sophisticated I have ever seen. Note also that
Level has an intValue() method and does not use object identity in
the equals(obj o) method. This is further indication that the typesafe enum
pattern is still evolving.
Why not make Level more widely available as an EnumeratedType
baseclass in that java.util package? There are basically two arguments
against creating a baseclass to make it easier to use typesafe enums:
• enum is not really needed in an object-oriented programming language (a
“code smell” in the terminology of the Portland Pattern Repository's Wiki,69
which is a really fun resource I discovered while writing this section)
• Enumerated types should be allowed to evolve into “full fledged abstrac-
tions”
Both of these are very reasonable arguments. The first argument ranges from
Bloch pointing out that enum tags are no longer required in (discriminated)
unions to replacing switch statements with the State design patterns. The lat-
ter can be traced back to Design Patterns:
Operations have large, multipart condition statements that depend of
the object’s state. This state is usually represented by one or more
enumerated constants. Often, several operations will contain this same
conditional structure. The State pattern puts each branch of the condi-
tional in a separate class. This lets you treat the object’s state as an
object in its own right that can vary independently from other objects.70

69. See c2.com/cgi/wiki?WelcomeVisitors or c2.com/cgi/wiki?CodeSmell.


70. Erich Gamma et al, 306.

FIELDS AND METHODS 119


The State design pattern is only one of many that have replaced the use of
switch statements (sometimes referred to as a case statement or multi-way
branch) and deeply nested if-then-else statements (sometimes referred to
as a conditional statement as in the above quote) as a poor substitute for
dynamic method lookup.71 Two others that come readily to mind are the
Strategy design pattern and dynamic proxies. There are many others though;
the list is practically endless. As replacements for switch and nested if-then-
else statements all of them have one thing in common.

Adding new behavior to either a switch or nested if-then-else


statement requires a manual update, which is a serious maintenance
issue. Behavioral design patterns do not.

Both of these arguments against an enumerated type baseclass, however, over-


look the common case of a discrete set of integers that have nothing whatso-
ever to do with behavior. As mentioned above, the Level class is the perfect
example of such a class. The enumerated constants in the Level class are pri-
marily used in the following if statement to determine if a log record should be
published.

if (level.intValue() < levelValue || levelValue == offValue) {


return;
}

Nor is Level ever going to evolve into a “full fledged abstraction.” Levels are a
discrete set of integers and nothing else. I have deliberately repeated this point
because it is central to this entire section.
The real reason for not creating an EnumeratedType baseclass in the
java.util package is that it will be subject to the worst kind of abuse, per-
petuating seriously outdated design ideas that evolved in procedural program-
ming languages, and that have no place in an object-oriented programming

71. The term polymorphism is normally used in this context. However, I define polymorphism much
more narrowly than the average programmer. See 5.4 Substitution is a Higher Concept than Poly-
morphism.

120 JAVA RULES


language such as Java. The issue of timing should not be lightly dismissed. Pat-
terns are still new to mainstream business application programmers, and if an
enumerated type baseclass is introduced too soon it will be as abused as
Hashtable has been (from an object-oriented design perspective). On the
other hand, assuming that application programmers know how to properly use
enumerated types, such a baseclass would eliminate a lot of boilerplate from
subclasses that implement the typesafe enum pattern. When faced with such a
dilemma, language designers must always assume that programmers know
what they are doing.
The design imperatives for an EnumeratedType baseclass are as fol-
lows.
• The single most important design criteria for an enumerated type baseclass
is that subclasses require an absolute minimum of code. Declaring an
object-oriented enumerated type should be almost as easy as declaring con-
venience constants. The only additional code required of subclasses should
be the class header and a single constructor.
• The data type in which the integer value of the enumerated constant is
stored should be a long in anticipation of 64-bit architectures becoming
the norm. Until then there will be a slight performance penalty to pay, but
the increased flexibility of having a 64-bit value (or 32 extra bit flags) simply
cannot be sacrificed.
• The constructor must provide a protected superclass implementation
hook so that subclasses can introduce constraints such as values being a
power of two.
• The enumerated type baseclass must implement Serializable without
requiring any additional code in subclasses. The readResolve()
method for such a baseclass would no doubt be a challenge, but is clearly
doable.
• The enumerated type baseclass must be internationalized, allowing base-
classes to pass localization resource bundles using the same “take it or
leave it” approach as the logging API.
• The enumerated type baseclass should be introduced at the same time as
the immutable class modifier and should be an immutable class. This

FIELDS AND METHODS 121


will allow additional time for the gospel of the State and Strategy design pat-
terns to spread.
Unless a baseclass for enumerated types is introduced to the Java programming
language, I see application programmers fragmenting into three groups: those
who side with Roubtsov and continue to use convenience constants; those who
understand the typesafe enum pattern enough to implement a simple typesafe
enum such as the examples in this section; and those select few who can prop-
erly implement a complex typesafe enum that is both serializable and internation-
alized.
I want to close this section by reiterating my strong belief that should Sun
implement an enumerated type baseclass it should be specifically designed to
support bitwise programming. This only makes sense if you fully understand the
design limitations of an object-oriented enumerated type (discrete sets of inte-
gers that have nothing to do with behavior). It is one of the design imperatives
listed above; one that I think is second only to getting rid of the boilerplate. Con-
sider the logging API. The cost of an unpublished log record could have been
reduced by using the bitwise operators. For example,

class Test {
private int levelValue =
java.util.logging.Level.SEVERE.intValue();
private static final int offValue =
java.util.logging.Level.OFF.intValue();
private int level = Level.Constants.SEVERE;

public static void main(String[] args) {


test1(10000000);
test2(10000000);
test1(100000000);
test2(100000000);
test1(1000000000);
test2(1000000000);
}
static void test1(int count) {
Test test = new Test();
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
test.log(java.util.logging.Level.FINER, "dummy message");

122 JAVA RULES


}
timer.stop();
System.out.println(count +
" using java.util.logging " +
timer.getElapsedTime());
}
static void test2(int count) {
Test test = new Test();
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
test.log(Level.Constants.FINER, "dummy message");
}
timer.stop();
System.out.println(count +
" using bitwise operators " +
timer.getElapsedTime());
}
void log(java.util.logging.Level level, String msg) {
//this is the exact same if statement used throughout
//the java.util.logging package
if (level.intValue() < levelValue ||
levelValue == offValue) {
return;
}
assert false;
}
void log(int level, String msg) {
//this is a non-public method so the faster interface
//constants (that emphasize speed) are passed
if ((level & this.level) != 0) {
return;
}
assert false;
}
}
final class Level {
private final String name;
private final int value;
private Level(int value, String name) {
boolean legal = false;
if (value == 0) {
legal = true;
} else {
for (int i=0; i<32; i++) {

FIELDS AND METHODS 123


if (value == ~((1 << i) - 1)) {
legal = true;
break;
}
}
}
if (!legal)
throw new IllegalArgumentException
(BitPattern.toBinaryString(value));
this.value = value;
this.name = name;
}
public int intValue() { return value; }
public String toString() { return name; }

//pass values (emphasize type safety)


public static final Level OFF =
new Level(Constants.OFF, "OFF");
public static final Level SEVERE =
new Level(Constants.SEVERE, "SEVERE");
public static final Level WARNING =
new Level(Constants.WARNING, "WARNING");
public static final Level INFO =
new Level(Constants.INFO, "INFO");
public static final Level CONFIG =
new Level(Constants.CONFIG, "CONFIG");
public static final Level FINE =
new Level(Constants.FINE, "FINE");
public static final Level FINER =
new Level(Constants.FINER, "FINER");
public static final Level FINEST =
new Level(Constants.FINEST, "FINEST");
public static final Level ALL =
new Level(Constants.ALL, "ALL");

//comparison values (emphasize speed and convenience)


public interface Constants {
int OFF = 0;
int SEVERE = ~((1 << 28) - 1);
int WARNING = ~((1 << 24) - 1);
int INFO = ~((1 << 20) - 1);
int CONFIG = ~((1 << 16) - 1);
int FINE = ~((1 << 12) - 1);
int FINER = ~((1 << 8) - 1);

124 JAVA RULES


int FINEST = ~((1 << 4) - 1);
int ALL = ~0;
}
}

The Microbenchmark class is a renamed version of the Stopwatch class


in 3.1 Benchmarking in Java Platform Performance,72 which is part of the
Addison-Wesley Java Series and an absolute must have for any professional
Java programmer. Note also that when executing microbenchmark tests in this
book I always target the latest release. In this book, that was accomplished
by always compiling with the -source 1.4 option. This alone can make a sig-
nificant difference in microbenchmark tests such as these. Here is some typi-
cal output on my computer:

10000000 using java.util.logging 328


10000000 using bitwise operators 62
100000000 using java.util.logging 2704
100000000 using bitwise operators 296
1000000000 using java.util.logging 27000
1000000000 using bitwise operators 3032

This example involves a test to see if trace information should be logged. It sim-
ulates a logger that is set to Level.SEVERE. Thus the test fails every time.
This is very common in production because trace information is typically not pub-
lished unless there is a problem. This “cheap comparison” (as it is described in
the following quote) is coded exactly as it is in all of the logging methods in Table
6.8 Logging Methods on page 976. It is described as follows in the API docs.
The APIs are structured so that calls on the Logger APIs can be cheap
when logging is disabled. If logging is disabled for a given log level,
then the Logger can make a cheap comparison test and return.73

These test results show that a greater appreciation for bitwise program-
ming could have significantly reduced the cost of this comparison. This
test actually favours the existing implementation in that the second Boolean

72. Steve Wilson and Jeff Kesselman, Java Platform Performance: Strategies and Tactics,
(Reading: Addison-Wesley, 2000), Listing 3-2, “Reusable stopwatch class.”
73. Unascribed, “Java Logging Overview” in the API docs for the 1.4.0 release, (Mountain View: Sun
Microsystems, 2002), §1.1, “Overview of Control Flow.”

FIELDS AND METHODS 125


expression to the right of the conditional-or operator (which is completely unnec-
essary when using the bitwise operators) is never evaluated. For client program-
mers there is no difference whatsoever in these two approaches. The only
possible objection is that there are not as many extra levels in between the nine
standard levels. In other words, subclasses can introduce at most 23 additional
levels (55 if a long were used instead of an int). At the point at which this
becomes an issue I would have to say that “the needs of the many outweigh the
needs of the few.” Every effort should have been made to reduce the cost of this
logging test.

1.5.6 Declaring Local Variables and Parameters final


A local variable or parameter declared final is like any other constant. The
value cannot be changed in the method or constructor body. Before the 1.1
release only fields could be declared final. Declaring local variables and
parameters final is a change to the language that was introduced as follows in
the Inner Classes Specification.
Local variables and parameters of all sorts can now be declared
final… Such a variable is subject to the usual definite assignment
rules governing local variables. In addition, it cannot be assigned to,
except for initialization.

A method parameter or catch formal parameter may be declared


final. This has no effect on the method signature or the caught
exception type. Within the body of the method or catch, the parameter
may not be assigned to.

The final declaration modifier may be used to make local variables


and parameters available to inner classes.74

I quote this specification to show that the raison d'être for declaring local vari-
ables and parameters final is to make them “available to inner classes” Actu-
ally only block classes (local and anonymous classes) can use local variables
and parameters declared final.

74. Rose, “Other changes in the Java 1.1 language.”

126 JAVA RULES


This language design decision is not adequately explained in the Inner
Classes Specification. In fact, the only explanation is the following rather short
passage (which seems more like a dodge):
Because of potential synchronization problems, there is by design no
way for two objects to share access to a changeable local variable.75

Elsewhere the Inner Classes Specification says that the copy of a local vari-
able or parameter that is passed to a block class constructor “never contain
inconsistent values”76 because they are declared final. That is the extent to
which the Inner Classes Specification explains why local variables and param-
eters used in block classes must be declared final, and the JLS is silent on
the subject. The remainder of this section will explain this language design deci-
sion.
There are no “potential synchronization problems” because local variables
and parameters are allocated in the local variable array on the stack, which is
not shared memory. “Shared access” is therefore an impossibility. This lan-
guage design decision is intended entirely to correct the mistaken perception
that a local variable or parameter (that exists only in a local variable array in a
single frame on the stack) could actually be shared between classes. For exam-
ple,

class Test {
public static void main(String[] args) {
int x = 0;
class LocalClass {
void print() {
System.out.println(x);
}
}
LocalClass local = new LocalClass(); //value of x passed here
local.print();
x = 10;
local.print();
}
}

75. Rose, “What are top-level classes and inner classes?”


76. Rose, “How do inner classes work?”

FIELDS AND METHODS 127


Ignoring the fact that this does not actually compile, executing this program
would print

0
0

The value of x is passed to the local class constructor when new Local-
Class() is evaluated. All classes, including local and anonymous classes, are
top-level package members after compilation. The transformed code looks like
this:

class Test {
public static void main(String[] args) {
int x = 0;
Test$1$LocalClass local = new Test$1$LocalClass(x);
local.print();
x = 10;
local.print();
}
}
class Test$1$LocalClass {
private int val$local;
LocalClass(int val$local) {
this.val$local = val$local;
}
void print() {
System.out.println(val$local);
}
}

Now you can clearly see why the value 10 is not printed. The decision to require
that local variables and parameters used in a block class be declared final is
not based on “potential synchronization problems.”77 It is intended entirely to
correct the mistaken perception that changing the value of a local vari-
able or parameter after a block class has been instantiated could some-
how change the corresponding value in the block class. I really do not want
to be seen as questioning this language design decision. They really had no
choice because the mechanism for passing the value of a local variable or

77. Rose, “What are top-level classes and inner classes?”

128 JAVA RULES


parameter to an inner class is completely hidden from view. Therefore, assum-
ing that the above program would print 10 after changing the value of the local
variable is perfectly natural. By forcing you to declare the local variable or
parameter final, the language designers effectively remove any possibility of
that assumption. Nevertheless, it should be pointed out that preventing further
changes in the value of a local variable or parameter after it is passed to a block
class is somewhat arbitrary.
The Inner Classes Specification included a clever workaround if you really
want to change the copy of a local variable passed to a block class. The sugges-
tion is to pass the value in a “one-element array”78 (which is sometimes referred
to as “passing by reference”). For example,

class Test {
public static void main(String[] args) {
final int[] x = {0};
class LocalClass {
void print() {
System.out.println(x[0]);
}
}
LocalClass local = new LocalClass(); //value of x passed here
local.print();
x[0] = 10;
local.print();
}
}

This program does compile and when executed prints:

0
10

I would note, however, that there really are “potential synchronization prob-
lems”79 in shared access to a one-element array.
Beyond their use in block classes, declaring local variables and method or
constructor parameters final is useful in documenting that the value never

78. Rose, “What are top-level classes and inner classes?”


79. Ibid.

FIELDS AND METHODS 129


changes. It is hard to image, however, why anyone would declare an exception
handling parameter final, unless for some reason the catch block included
a lot of code.

NOTE 1.1
Patterns are good programming practices. An antipattern is a bad one.
Inasmuch as Joshua Bloch both defined and solved the problem of the
Constant Interface antipattern (his term), it would be reckless not to
attribute the following section to him. It is merely a restatement of his
“Item 17: Use interfaces only to define types” in Effective Java.80

1.5.7 The Constant Interface Antipattern


Constants can be defined in either a class or interface. Prior to the 1.5 release,
however, only interface constants could be used without having to qualify them
with a type name. This is accomplished by implementing the interface. By con-
trast, importing a class only makes it possible to use the simple name of the

Importing a class does not make it possible to use the simple names
of static members, but implementing an interface does. This is a
little confusing at first.

class. The static members in that class must still be qualified with the class
name. Thus programmers have evidenced a strong preference for declaring
constants in interfaces rather than in classes. (The opponents of the new
import static facility in the 1.5 release should remember this.)
This was the status quo before Bloch pointed out that public interfaces are
part of the API design, and should not be used merely to declare constants. For
example, what does it mean for a class to implement java.swing.Swing-
Constants ? Is a JProgressBar a SwingConstant in the polymorphic

80. Bloch, Effective Java, “Item 17: Use interfaces only to define types.”

130 JAVA RULES


sense? Obviously not! The java.swing package is merely using Swing-
Constants to make it easier to reference constants. The problem is that
doing so muddies the API design in the process.
In the past, the alternative has been to define the constants in a utility class
(or a typesafe enum if they are an enumerated type), and then assign their val-
ues to local variables or private static fields. For example,

final double PI = Math.PI;

The opponents of the import static facility would argue that using
Math.PI is much clearer than using just PI and does not run the risk of name
conflicts similar to those that occur when using type-import-on-demand declara-
tion. I do not necessarily disagree with them. This facility must be used with
care, especially when invoking static methods. See 2.6.5 import static
in the Second Edition of Volume 1 for a discussion.
One thing is evidently clear, however. The days of using interfaces to
declare constants (unrelated to a specific type) are over. Examples such as
SwingConstants in the core API are no less an anachronism than similar
uses outside of Sun. As Bloch says, such interfaces in the core API are “anoma-
lies and should not be emulated.”81 This practice is described as an antipattern
because the fact that a class uses constants such as those declared in
SwingConstants is an implementation detail that should not be exposed. In
other words, using interfaces to declare constants breaks the encapsulation of
any class that implements the interface.

1.6 Methods
A method declaration consists of a method header and method body. A
method body is one of the following:
• A semicolon indicating either (1) that an abstract method is not imple-
mented, or (2) that the body of a native method is omitted
• A pair of empty braces indicating a void method that returns without doing
anything

81. Ibid.

FIELDS AND METHODS 131


• A method implementation
Method bodies are more commonly referred to as the implementation of a
method. A pair of empty braces is referred to as an empty method implemen-
tation.
The five parts of a method header are summarized in Table 1.5. Method
modifiers are discussed in this section, but only the abstract and final
modifiers. The static modifier is discussed in 3.2.2 static Methods in Vol-
ume 1. The synchronized and native modifiers are discussed in an as yet
unpublished volume of Java Rules. The strictfp modifier is discussed in
4.3.2.2.2 The FP-Strict Floating-Point Mode (strictfp) in Volume 1. Other
parts of the method header are discussed in the following subsections.

Table 1.5 The Five Parts of a Method Header


Part Notes

b Method modifiers The method modifiers are the access modifiers (public,
protected, and private), abstract, static,
final, synchronized, native, and, as of the 1.3
release, strictfp.

c Result type The result type is either void (for methods that do not return
a value) or the type of the method invocation expression.
Notice this says result type. The term return type is a
bastardization of return statement and result type. There
is no such thing as a “return type,” because of the method
return conversion context discussed in 5.7.3 Method Return
Conversion Context.

d Identifier The name of the method

e Formal parameter list A comma-separated list of parameter specifiers enclosed in


(or parameter list for parentheses. The adjective formal as well as the term
short) parameter specifier are discussed below.

f throws clause The throws clause is a compiler-enforced exception


specification. Any checked exception potentially thrown during
the execution of a method will be listed in the throws
clause.

132 JAVA RULES


The JLS suggests ordering method modifiers as follows.
[public, protected, private]
abstract
static final synchronized native strictfp
This order “is customary, though not required.”82 In other words, it is a program-
mer convention. The order is not an arbitrary order, however. The method modi-
fiers are listed in the order in which they are most commonly used, starting with
the access modifiers and ending with native and the last modifier added to
the language, which is strictfp. The same order of the access modifier fol-
lowed by static and final is used in both field and method declarations.
The abstract modifier is shown on a separate line to emphasize that it
cannot be used in combination with any of the modifiers that follow. In fact,
public and protected are the only method modifiers that can be used
when declaring an abstract method. It is interesting to note that Bloch does
not observe the JLS convention of always placing the access modifier first when
he declares an abstract method. For example, the following declaration is
from AbstractArrayList.

abstract public Object get(int index);

Putting the abstract modifier in front of the access modifier is a very reason-
able exception to the general rule the access modifiers always come first
because there can be at most two modifiers in such a declaration.
Subclasses cannot declare methods with the same signature as final
superclass methods. Consequently, attempting to hide or override a final
method is a compiler error. The practical effect of declaring a method
private is to make it final because private methods are not inherited.
Likewise, all of the methods in a final class are implicitly final because the
class cannot be extended. Declaring such methods final is redundant, but
does not generate a compiler error.

82. Gosling et al., The Java Language Specification, §8.4.3, “Method Modifiers.”

FIELDS AND METHODS 133


The general intent of declaring an instance method final is that the behav-
ior cannot be changed in subclasses. If the implementation of a final instance
method does either of the following, however, subclasses can in fact change
their behavior.
• Invokes another instance method that can be overridden
• Accesses a non-private instance variable
You must always keep these two possibilities in mind when coding final
instance methods.

1.6.1 abstract Methods


The abstract modifier can be either a class or method modifier. Both uses
are closely related because any class that either declares or inherits
abstract methods without implementing them must itself be declared
abstract. As mentioned in the previous section, all abstract methods
must use one of the following three combinations of method modifiers:
public abstract
protected abstract
abstract
No other combination of method modifiers is possible for abstract methods.
For example,

abstract class Test {


private abstract void a() { }
abstract final void b() { }
abstract synchronized void c() { }
abstract native void d() { }
abstract strictfp void e() { }
}

Attempting to compile this program generates the following compiler errors:

Test.java:2: illegal combination of modifiers: abstract and


private
private abstract void a() { }
^
Test.java:3: illegal combination of modifiers: abstract and final

134 JAVA RULES


abstract final void b() { }
^
Test.java:4: illegal combination of modifiers: abstract and
synchronized
abstract synchronized void c() { }
^
Test.java:5: illegal combination of modifiers: abstract and
native
abstract native void d() { }
^
Test.java:6: illegal combination of modifiers: abstract and
strictfp
abstract strictfp void e() { }
^
5 errors

The explanation for these compiler errors is as follows.83


• Methods declared private are not accessible, and therefore are not
inherited by subclasses. If an abstract method is not inherited, it cannot
be implemented.
• Similarly, final methods cannot be overridden, and therefore an
abstract final method would be impossible to implement.
• The reason why static methods cannot be declared abstract is that
from an object-oriented design perspective it makes no sense for a super-
class to obligate a subclass to implement a class method.
• The native, synchronized, and strictfp modifiers describe the
implementation of a method, and abstract methods are not imple-
mented.
That leaves only the three combinations listed above.
One of them, however, has little or no practical use because an abstract
method with default access cannot be implemented in another package. For
example,

package com.javarules.examples;
public abstract class Superclass {

83. Before the 1.2 release, many of these illegal combinations of abstract and other field mod-
ifiers would compile. See Bug Id 1266571.

FIELDS AND METHODS 135


abstract void defaultAccess();
}

The following subclass is declared in the unnamed package.

import com.javarules.example.*;
public class Test extends Superclass {
void defaultAccess() {}
}

It would appear as if the Test subclass has implemented the default-


Access() method, but attempting to compile Test generates the following
compiler error:

Test.java:1: Test should be declared abstract; it does not define


defaultAccess() in com.javarules.examples.Superclass
public class Test extends Superclass {
^
1 error

The compiler must allow such declarations because the superclass can be
extended in the same package. Outside of that package, however, default
access (which means the method is not inherited) and the abstract modifier
(which requires that the method be implemented) are as contradictory as any of
the other illegal combinations of method modifiers listed above.
That leaves only public abstract, which is simply a way of declaring a
behavior that subclasses must implement before they can be instantiated, and
protected abstract. Subclasses inherit interface and implementation. If
methods declared abstract are not implemented, then subclasses can only
inherit interface from them. Given the definition of the protected access mod-
ifier in 2.8.1 The protected Access Modifier (as “implementation inherit-
ance”), a protected abstract method may seem to be an inherent
contradiction. Indeed, such methods have a very special purpose in object-ori-
ented programming. A protected instance method that is declared
abstract, has an empty method body, or a default implementation that must
be overridden by at least some subclasses is described as a superclass imple-
mentation hook in this book. These are very special methods discussed in 3.9
Designing Extensible Classes. Thus abstract methods have only two,

136 JAVA RULES


clearly defined uses. Either they are public and define a behavior that
must be implemented in subclasses before they can be instantiated or
else they are protected and are superclass implementation hooks.
Interface methods are implicitly public abstract. The explicit use of
method modifiers in an interface declaration is actively discouraged “as a matter
of style” in the JLS.
For compatibility with older versions of the Java platform, it is permit-
ted but discouraged, as a matter of style, to redundantly specify the
abstract modifier for methods declared in interfaces.
It is permitted, but strongly discouraged as a matter of style, to
redundantly specify the public modifier for interface methods.84
[emphasis added].

Unlike interface constants, the emphasized text was not removed in the Second
Edition. Apparently, no one thinks method modifiers should be used when declar-
ing methods in an interface.
The difference between an abstract method and an empty method imple-
mentation is subtle. Syntactically one is represented by a semicolon and the other
by empty braces. As stated above, an abstract method defines a behavior that
must be implemented somewhere in a class hierarchy before subclasses can be
instantiated. In other words, abstract are not implemented. Empty method
implementations are “do nothing” methods. One of the most common uses of
empty method declarations is in classes such as WindowAdapter that imple-
ment EventListener interfaces in the java.awt.event package. Such
classes are designed to simplify the implementation of anonymous classes used
as event adapters. For example,

addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});

84. Gosling et al., §9.4, “Abstract Method Declarations.”

FIELDS AND METHODS 137


There are six other events in the WindowListener interface that “do nothing”
because of the empty method implementations in the WindowAdapter super-
class extended by this anonymous class. Those empty method declarations
serves as default implementations in an anonymous class that extends
WindowAdapter.
The remainder of this section discusses a rather obscure terminological
point concerning abstract methods. Normally, abstract methods are
thought of as being implemented, not overridden. Methods that implement
abstract methods, however, do so by first overriding the abstract
method. This apparently minor usage distinction is important because the over-
riding method may itself be declared abstract. Unfortunately, the JLS does
not always take into account the possibility that the overriding method may be
abstract. For example,
A class type may contain a declaration for a method with the same
name and the same signature as a method that would otherwise be
inherited from a superclass or superinterface. In this case, the method
of the superclass or superinterface is not inherited. If the method not
inherited is abstract, then the new declaration is said to implement
it; if the method not inherited is not abstract, then the new declara-
tion is said to override it.85

If the overriding method is itself abstract, then it cannot implement anything.


As used in this section and elsewhere in Java Rules, the phrase “overriding an
abstract method” always implies that the overriding method is abstract.
Otherwise, “implementing an abstract method” is used instead. The idea of
overriding an abstract method may sound like incorrect usage to some pro-
grammers. Nevertheless, this usage is supported in the JLS. 86 There are
exactly two reasons for overriding an abstract method. Both are related
to the interface contract.

85. Gosling et al., The Java Language Specification, §6.4.2, “The Members of a Class Type.”
86. Gosling et al., §8.4.6.1, “Overriding (By Instance Methods).” See also §9.4.1, “Inheritance
and Overriding,” the opening sentence of which explicitly states that subinterface methods “over-
ride” superinterface methods.

138 JAVA RULES


The first and most important reason for overriding an abstract method is
to add documentation comments. Doing so potentially changes the semantics of
a method that will eventually be implemented in a subclass. The add(Object
o) method in the java.io.List interface is an important example of such a
method. It adds a restriction that duplicate elements cannot be added to a list.
The other reason for overriding abstract methods is to change the exception
specification. This is accomplished either by omitting a checked exception from
the throws clause of the overriding abstract method or by naming a sub-
class of an exception thrown by the overridden abstract method. For exam-
ple,

public abstract void getDefaultProperties() throws IOException

This method throws a general IOException. Now consider the following over-
riding method in a subclass.

public abstract void getDefaultProperties() throws


FileNotFoundException

Implementations of this method further down in the class hierarchy are now
restricted to throwing the more specific FileNotFoundException.

1.6.2 Result Types and the return Statement


The type of a method invocation expression is always the result type in the
header of a method declaration. The value of a method invocation expression is
the return value, unless the result type is declared void. A void method
returns nothing. The type of the return value must be assignable to the result
type in the method header. See 5.7.3 Method Return Conversion Context for a
detailed discussion. The method return conversion context effectively renders
the bastardized term return type meaningless, because of the possibility that
the expression in a return statement evaluates to a type that is not the same
as result type and must therefore be converted. The proper term is result type.
(Though I must add here that examples of return type can be found in the JLS.)
The question is sometimes asked: How can I return more than one
value? An array can be used to return more than one primitive data type,

FIELDS AND METHODS 139


assuming all of the values returned are the same primitive data type. If the result
type is an array, you should always return a zero-length array,87 never null.
For example,

class Widget {
private static final Widget[] OUT = new Widget[0];
private Widget[] stock;

public boolean inStock() {


return (stock.length > 0) ? true : false;
}
public Widget[] getStock() {
return (stock.length > 0) ? stock : OUT;
}
}

As shown in this example, zero-length arrays are immutable and can be returned
over and over again. This class caches a zero-length array during class loading.
The problem with returning null can be seen in the args parameter of the
main method. One must always test for null before attempting to access the
args array (to search for an element or even just to query the length field).
Client programmers are confronted with the same problem if you return null
instead of a zero-length array. As noted by Joshua Bloch in his best seller Effec-
tive Java:
…there is no reason ever to return null from an array-valued
method instead of returning a zero-length array. This idiom is
likely a holdover from the C programming language, in which array
lengths are returned separately from actual arrays. In C, there is no
advantage to allocating an array if zero is returned as the length.88

The newer Collections Framework should be used to pass a group of objects.


Beyond that the idea of “returning more than one value” becomes a problem in
terms of object-oriented design. Most reference types are “more than one value”
in the sense that they have more than one instance variable. Therefore if you

87. The term empty array cannot be used in this context. In fact, I do not use that term at all
because it can be interpreted to mean either a zero-length array or an array in which no values have
been assigned to the components.
88. Bloch, “Item 27: Return zero-length arrays, not nulls.”

140 JAVA RULES


want to return “more than one value” you need to be thinking about what those
values represent.
If the closing brace of a method body is reached, the method is said to com-
plete normally (normal completion), which is sometimes referred to as fall-
ing through a method (or fall through). The opposite of normal completion (or
fall through) is abrupt completion. Technically speaking, the execution of a
return statement is considered abrupt completion. See 4.8.3 Control-transfer
Statements (a.k.a. Abrupt Completion) for a discussion.
The last statement in every method not declared void must be either a
return or throw statement. Furthermore, that statement must be uncondi-
tionally executed. Fall through (on any execution path) is a compiler error unless
the method is declared void. For example,

private FileDescriptor fd;



public final FileDescriptor getFD() {
if (fd != null) return fd;
}

Attempting to compile this program generates the following compiler error:

Test.java:4: missing return statement


public final FileDescriptor getFD() throws IOException {
^
1 error

The problem with the return statement in this accessor method is that it is
conditionally executed. If fd == null is true, the method will fall through.
Here is the actual method declaration from the RandomAccessFile class of
the java.io package:

private FileDescriptor fd;


public final FileDescriptor getFD() throws IOException {


if (fd != null) return fd;
throw new IOException();
}

FIELDS AND METHODS 141


Why throw an IOException? Good question. There may be some historical
explanation. The point here is that you do not necessarily have to code a
return statement. The compiler only checks for fall through, not for a
return statement. Therefore unconditionally throwing an exception at the bot-
tom of a method (including void methods) is always an option. Doing so in a
method that includes one or more conditionally executed return statements is
comparable to an elaborate switch statement that includes a default label.
If none of the other execution paths are followed, the default is to throw an
exception. This is a common programming technic. There is a closely related
discussion in 6.2.2 assert false and Logic Traps (or Control-flow Invariants).
Early releases of Java allowed “some or all”89 of the empty brackets from
the result type of a method that returns an array to appear after the former
parameter list. For example:

public Class getInterfaces() []

The result type of the getInterface() method is Class[], not Class.


This is not like declaring an array variable in which there are two opposing
camps (regarding the placement of the empty brackets) and the no man’s land
of mixed notation. This syntax is potentially very misleading because the empty
brackets at the end of a parameter list (especially a long one) are easily over-
looked. The correct declaration is as follows.

public Class[] getInterfaces()

Placing the empty brackets after the former parameter list in a method declara-
tion is still supported for backward compatibility with those early releases of
Java. However, all editions of the JLS have specified that this syntax “should not
be used in new code.”90 See also 1.11.1.2 Covariant Result Types.

1.6.2.1 Using Return Values to Indicate Failure


In the C programming language, functions that normally return a nonnegative
integral value may indicate failure by returning -1 . The -1 is sometimes

89. Gosling et al., The Java Language Specification, §8.4, “Method Declarations.”
90. Ibid.

142 JAVA RULES


referred to as an out-of-band signal (or out-of-band return value). Although
the term out-of-band signal is established usage, the Free On-Line Dictionary
of Computing (or FOLDOC) includes the following usage note.
This use confuses “out-of-band” with “out-of-range”. It is actually a clear
example of in-band signalling since it uses the same “channel” for con-
trol and data.91

This perhaps explains why the JLS uses funny values instead, and Bloch uses
distinquished return value in Effective Java. I have also heard them referred
to as special values.
Java exceptions are intended to replace the use of return values to indicate
failure. As stated in the JLS:
Explicit use of throw statements provides an alternative to the old-
fashioned style of handling error conditions by returning funny values,
such as the integer value -1 where a negative value would not normally
be expected. Experience shows that too often such funny values are
ignored or not checked for by callers, leading to programs that are not
robust, exhibit undesirable behavior, or both.92

A small number of methods in the core API return -1, but not to signal failure.
Methods in the java.io and java.nio package use -1 to signal EOF, and
the String and StringBuffer classes return -1 to indicate that a char
or substring was not found. What is happening on the Java platform is that
null is being returned instead of an object. In 6.9 Exception Handling, I argue
that this is not one whit different than returning -1 to indicate failure. Such meth-
ods should either throw an exception or else use the null object pattern. There
are also a handful of boolean methods in the core API that return false to
indicate failure. This is also a mistake. Return values should not be used to indi-
cate failure in the Java programming language. The problem with doing so is

91. The Free On-Line Dictionary of Computing (or FOLDOC) is available at


foldoc.doc.ic.ac.uk/foldoc/index.html. This is perhaps the most plagiarized work on
the whole of the Internet. I regard it as absolutely indispensable and could not have written Java
Rules without it.
92. Gosling et al., Introduction to Chapter 11, “Exceptions.”

FIELDS AND METHODS 143


widely understood; there is nothing like checked exceptions to make sure client
programmers consider the possibility of failure.

1.6.3 Formal Parameter Lists


The formal parameter list in a method or constructor header is a comma-sep-
arated list of parameter specifiers. If there are no formal parameters, empty
parentheses are still required syntax in a method or constructor header.
The adjective formal is used to help differentiate the parameters in a
method or constructor header from the “actual” arguments (or values passed)
when a method invocation or class instance creation expression is evaluated.
This distinction between the formal parameter and actual argument is
respected by almost every software engineer and technical writer, so much so
that the adjectives formal and actual are not really necessary. I use them in
some contexts. (The term formal argument is a bastardization that should not
be used.)
It is useful to think of all entities as being declared somewhere in a Java pro-
gram. However, one does not usually speak of “parameter declarations” (per-
haps because the syntax for declaring a parameter is so simple in comparison
to field and local variable declarations). The JLS uses the term parameter
specifier instead:
The formal parameters of a method or constructor, if any, are speci-
fied by a list of comma-separated parameter specifiers. Each parame-
ter specifier consists of a type (optionally preceded by the final
modifier) and an identifier (optionally followed by brackets) that speci-
fies the name of the parameter.93

A parameter declared final is like any other constant. The value cannot be
changed in the method or constructor body. See 1.5.6 Declaring Local Variables
and Parameters final for a discussion.
Parameters are created when a method invocation expression or class
instance creation expression is evaluated and destroyed when control passes
out of the method or constructor either because a return or throw state-

93. Gosling et al., §8.4.1 Formal Parameters.”

144 JAVA RULES


ment is executed or because a void method or constructor completes nor-
mally by reaching the closing brace.
Parameters are initialized with the value of the corresponding argument
expression (which is sometimes referred to as “passing by value”).94 If the
parameter type is a primitive data type, the formal parameter is effectively a
copy of the value passed (the actual argument). Making a change to the copy
does not change the original value. For example,

class Test {
public static final void main(String[] args) {
int i = 100;
change(i);
System.out.println(i);
}
static void change(int value) {
value--;”
}
}

Executing this program prints "100". Initializing a reference type parameter,


however, results in another reference to the same object (sometimes called
an alias). If the object is immutable, aliasing is not an issue. If the object is muta-

Java does not pass objects, only references to objects. Therefore


phrases such as “passing an object” (which should be considered
proper usage) always imply “passing a reference to an object.”

ble, however, one of two things can happen at this point. If a defensive copy is

94. There is a mindless debate that rages over whether Java passes objects “by value” or “by refer-
ence.” This terminology is used nowhere in either the JLS or JVMS. It is a throwback to the C
and C++ programming languages. Furthermore, to argue that Java passes objects “by value”
because a copy of the reference is passed is superficial analysis. The real issue is that Java does
not create objects on the stack. Therefore, there is no mechanism for automatically creating a copy
of the object passed. In short, Java cannot pass an object. Java can only pass references to
objects. What possible good can come from arguing that Java passes those references “by value”
because they are “copies” of the original? To do so is merely to explain how parameters are initial-
ized. They are initialized with a copy of the corresponding argument expression. Whether that is
considered the equivalent of “passing by value” or “passing by reference” is irrelevant to Java pro-
grammers who have no background in the C and C++ programming languages (which sooner or
later will be the most of them).

FIELDS AND METHODS 145


made, the formal parameter no longer references the object passed. Otherwise,
the alias refers to the same mutable object that was passed. Changes to that
object can be seen in both methods. For example,

class Test {
public static final void main(String[] args) {
Mutable mutable = new Mutable();
System.out.println(mutable);
change(mutable);
System.out.println(mutable);
}
static void change(Mutable value) {
value.mutatorMethod();
}
}
class Mutable {
int i = 100;
void mutatorMethod() {
i--;
}
public String toString() { return Integer.toString(i); }
}

Executing this program prints

100
99

This is precisely why immutable objects are so invaluable in object-oriented pro-


gramming. You never have to be concerned with another method changing the
value of an immutable object. See also 1.6.3.3 Making Defensive Copies.

NOTE 1.2
Everything said in the following section applies to constructors as well
as to methods. There is a dearth of argument checks in constructors
(including constructors in the core API). The arguments in a class in-
stance creation expression are not substantially different from those in
a method invocation expression. Generally speaking, they should be

146 JAVA RULES


checked. Failure to do so in a constructor is one of those problems that
is so pervasive on the Java platform as to go unnoticed.

1.6.3.1 Argument Checks


The type checking performed by a compiler is sometimes not enough to make
sure that the values passed to a method or constructor are valid. For example,
the API docs for the wait(long timeout, int nanos) method in the
Object class include the following.
Parameters:
timeout - the maximum time to wait in milliseconds.
nanos - additional time, in nanoseconds range 0-999999.
Throws:
IllegalArgumentException - if the value of timeout is
negative or the value of nanos is not in the range 0-999999.

This documentation is automatically generated by using the @param and


@throws tags. As you can see these documentation comments inform client
programmers that the timeout argument cannot be negative, and they limit
the nanos (which when added to timeout is the total time to wait) to less
than one second.
The code that makes sure that the client programmer conforms to this spec-
ification is referred to as an argument check.95 These are simple if state-
ments that should be coded at the very top of a method or constructor body so
as to avoid the necessity of consistent state restoration (as discussed in 6.9
Exception Handling). For example,

public final void wait(long timeout, int nanos)


throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException(
"timeout value is negative");

95. Prior to the publication of the “Programming With Assertions” in the 1.4 release in which asser-
tions were introduced to the Java programming language, I used the term parameter check. The
change from parameter check to argument check is consistent with my policy of always deferring
to the software engineers and technical writers at Sun in matters of terminology.

FIELDS AND METHODS 147


}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}

There is one important exception to coding argument checks at the very top of a
method or constructor. As discussed in 1.6.3.3 Making Defensive Copies, argu-
ment checks should always be made against the defensive copy. Therefore the
code to make the defensive copy comes first.
Argument checks such as these cannot be coded without documenting them
using the @param and @throws tags because they throw runtime exceptions
that are not included in the throws clause of a method or constructor header.
They are always used in public and protected methods, but in default
access and private methods assertions are preferable to arguments checks
for reasons explained in 6.2 Assertions.
There are two very specific reasons why assertions cannot be used as
replacements for argument checks in public and protected methods: they
can be disabled; and if the Boolean expression in an assertion evaluates to
false an AssertionError is thrown. Either of these would violate the
exception specification in the API docs. Disabling assertions violates the excep-
tion specification because the exception would not be thrown. Throwing
AssertionError would violate the exception specification because the
method is documented as throwing IllegalArgumentException (or
some other runtime exception). Normally neither runtime exceptions nor errors
are explicitly caught, but changing from a runtime exception to an error could
change the behavior of a program that uses a catch(Exception e) catch-
all exception handler.
Here is the actual documentation comments in the Object class that result
in the API docs that you see above:

* @param timeout the maximum time to wait in milliseconds.


* @param nanos additional time, in nanoseconds range
* 0-999999.
* @exception IllegalArgumentException if the value of

148 JAVA RULES


* timeout is negative or the value of nanos is
* not in the range 0-999999.

The @throws tag was introduced in the 1.2 release as a “synonym” for
@exception with the explanation that throws is the keyword and so
@throws is generally preferred over @exception. Both are translated into
“Throws:” by javadoc. The @throws tag is discussed at length in 1.6.4
The throws Clause.
In the examples above, “in nanoseconds range 0-999999”, “if the value of
timeout is negative”, and “if newLocale is null” are known as precondi-
tions, which the Cambridge International Dictionary of English defines as “some-
thing which must happen or be true before it is possible for something else to
happen.”96 Preconditions are mentioned in 6.2.1 Preconditions, Postconditions,
and Invariants for the sake of completeness, but are primarily discussed in this
section. The relationship between argument checks and preconditions is that the
former are used to test that client programmers are in compliance with the lat-
ter. Other tests are required to test preconditions, however. For example, tests
that throw IllegalStateException are not usually argument checks.
Thus the term precondition has a broader meaning than argument checks.
Preconditions can be documented using @param tag, the @throws tag, or
both. Here is an example from the java.nio package that documents the pre-
conditions using the @param tag:
Parameters:
newPosition - The new position value; must be non-nega-
tive and no larger than the current limit

Throws:
IllegalArgumentException - If the preconditions on
newPosition do not hold

It seems more natural to me to document preconditions using the @param tag


(as shown here), but whether you do so and repeat them in the @throws tag,
or only document them in the @throws tag is purely a matter of style.

96. See dictionary.cambridge.org.

FIELDS AND METHODS 149


The exceptions thrown in argument checks are usually runtime exceptions
declared in the core API, the most common of which are listed in Table 1.6. Note

Table 1.6 Runtime Exceptions Commonly Thrown by Argument Checks


Standard RuntimeExceptiona Notes

IllegalArgumentException This is a catchall exception and undoubtedly


the most common RuntimeException
thrown by argument checks. It should be used
when none of the other, more specific
standard runtime exceptions are appropriate.

IllegalStateException The concept of “consistent state” is discussed


at length in 6.9 Exception Handling. If the
argument(s) passed to a method or
constructor violate some precondition and
therefore would result in an illegal (or
“inconsistent”) state, this exception is thrown.

NullPointerException Thrown if the value of a reference type


parameter is null.

IndexOutOfBoundsException This is the general-purpose exception for a


bounds check.

UnsupportedOperation- This exception is not used in “argument


Exception checks” per se, but is usually included in lists
such as this. I personally do not think methods
should throw this exception. The Collections
Framework was a special case in which there
were overriding API design issues. Beyond
that, however, the notion of “unimplemented
behavior” is extremely poor object-oriented
design.
a. These exceptions are members of the java.lang package.

that IndexOutOfBoundsExcpetion is always the exception class name


that should be used in an @throws tag. The ArrayIndexOutOfBounds
and StringIndexOutOfBounds subclasses are considered to be imple-
mentation specific. This is a very short list. Nevertheless, I encourage you to try

150 JAVA RULES


to use one of these standard runtime exceptions before getting creative. As an
example of getting creative, the java.math package makes heavy use of
ArithmeticException in argument checks. For example,

public BigDecimal divide(BigDecimal val,


int scale,
int roundingMode) {
if (scale < 0)
throw new ArithmeticException("Negative scale");
if (roundingMode < ROUND_UP||roundingMode >ROUND_UNNECESSARY)
throw new IllegalArgumentException("Invalid rounding mode");

This usage is inconsistent with the rest of the core API, which throws an
IllegalArgumentException under the same circumstances. Throwing
ArithmeticException instead anticipates something that has not as yet
happened.
When coding argument checks for strings, including a check for an empty
string (i.e., "" or a string that has no length) is often as important as checking
for null. In fact, you should make it a point to stop and consider the implica-
tions of passing an empty string when coding any method or constructor with
String type parameters. Usually an IllegalArgumentException is
thrown as a result. It may be tempting to code the following, but throwing
NullPointerException for an empty string is clearly misleading.

if (s == null || s.length = 0)
throw new NullPointerException

If you want to code a single argument check such as this, throw Illegal-
ArgumentException instead. Be sure to mention both null and empty
strings in the @throws tag documentation. Forgetting to check for an
empty string is a very common problem. For example, in Bug Id 4481055,
“LOGGING APIs: Undefined behavior for FileHandler constructors with
empty pattern” none of the constructors in the FileHandler class check for
an empty string. There is at least one other example of this in the logging API,
which is documented in Bug Id 4486791, “LOGGING APIs: Undetermined behav-
ior on empty logger names.” My reason for picking on the logging API is that the

FIELDS AND METHODS 151


SocketHandler(String host, int port) constructor is perhaps an
example of when an empty string should be considered a valid argument. Doing
so would be consistent with InetAddress.getByName(String host)
which, though undocumented, behaves exactly the same when either null or
an empty string is passed. This issue will be decided by Bug Id 4398380, “LOG-
GING APIs: SocketHandler is created with empty host name.” Checking for
an empty string may at first seem unnecessary. For example, who is going to
use an empty string as a pattern argument when instantiating File-
Handler ? Arguments such as this, however, fail to realize that some program-
mers initialize String type fields with empty strings (precisely so that they will
not throw a NullPointerException). In that case, passing an empty
string is the equivalent of passing null.
Sometimes argument checks can be omitted as a minor performance opti-
mization because the runtime system will throw the exception anyway. For exam-
ple,

/**
* Sets the time field with the given value.
* @param field the given time field.
* @param value the value to be set for the given time field.
*/
public final void set(int field, int value) {
fields[field] = value;
}

This is a simplified version of the set(int field, int value) method in


the Calendar class. The design of the Calendar class is such that any
value can be passed, but what happens if a field not in the range of 0-16 is
passed? An ArrayIndexOutOfBoundsException is thrown. The respon-
sible programmer could have added a bounds check to the set(int field,
int value) method, but the only difference would have been that a different
exception is thrown (the general-purpose IndexOutOfBoundsException).
An explicit argument check can be omitted in such a case.
For the sake of having a name, let us call this the system-induced argu-
ment check performance optimization, which is a rather long but descriptive
name. System-induced argument checks should only result in runtime exceptions

152 JAVA RULES


that are thrown in the same method or constructor to which the argument is
passed. Otherwise, the source of the exception tends to become buried in a
stack trace. For example,

Exception in thread "main"


java.lang.ArrayIndexOutOfBoundsException
at java.util.Calendar.internalSet(Unknown Source)
at java.util.Calendar.set(Unknown Source)
at Test.main(Test.java:5)

This is a stack trace for the ArrayIndexOutOfBoundsException thrown


by the set(int field, int value) method in the Calendar class. This
is not so bad (though I would have opted for an explicit argument check)
because the very next method invoked threw the exception, but where do you
draw the line? For example, evaluating new Locale(null) throws the follow-
ing exception:

Exception in thread "main" java.lang.NullPointerException


at java.util.Locale.toLowerCase(Unknown Source)
at java.util.Locale.convertOldISOCodes(Unknown Source)
at java.util.Locale.<init>(Unknown Source)
at java.util.Locale.<init>(Unknown Source)
at Test.main(Test.java:4)

Here the exception is thrown in a private method, which can be very confus-
ing to client programmers reading a stack trace. (All of the constructors in the
Locale class are documented to throw NullPointerException, so I
assume the absence of an explicit argument check is an example of a system-
induced argument check performance optimization and not just an oversight.)
NullPointerException is the classic system-induced argument check.
Many other methods and constructors in the core API doubtless make the same
mistake. In fact, explicit argument checks for null references are often omitted
even when it is painfully obvious that passing a null reference is eventually
going to result in NullPointerException or some other exception being
thrown. I would argue that, if you are going to use the system-induced argument
check for a NullPointerException, the first use of the parameter
should be as a target reference in a field access or method invocation

FIELDS AND METHODS 153


expression. The following code from the java.util.logging.Level is
interesting in this regard:

public LogRecord(Level level, String msg) {


// Make sure level isn't null, by calling random method.
level.getClass();

I first saw this in the java.util.logging package, where it is done more


than once. This is what might be called a deliberate system-induced argu-
ment check. It replaces the following two lines of code.

if (name == null)
throw new NullPointerException();

As both a matter of style and performance, I think coding something like


level.getClass() instead of an explicit argument check is highly question-
able. It may even be characterized as lazy. Not only is the intent muddled, but
the best performance that could be hoped for would be on a par with explicitly
checking for null. It is easy to see how setting such an example could lead to
abuses in which expensive instance methods are invoked as a means of check-
ing for null.

If the first use of a reference-type parameter is not as a target refer-


ence in a field access or method invocation expression, then an
explicit argument check should be coded as a means of checking for
null parameters.

This includes something as simple as assigning the parameter to a field, but is


especially important if the first use is as an argument expression. Reference
type parameters generally should not be passed out of a method or constructor
without first checking to make sure they are not null. Some “robust” methods,
however, can ignore a null reference. An equals(Object obj) method
that returns false if a null reference is passed is an important example.
Beyond the difficulty of reading stack traces in cases like new Locale
(null) is the more important issue of documentation. The exceptions thrown
by argument checks should be documented using the @throws tag. Once doc-

154 JAVA RULES


umented, the throwing of a runtime exception becomes part of the interface con-
tract for that method or constructor. When it comes to documenting the
exceptions thrown by argument checks, it should not matter if the exception
is explicitly thrown in an argument check at the top of the method or
constructor or if it is implicitly thrown by the runtime system somewhere
further down in the method or constructor body. That is an implementation

System-induced argument checks should be documented exactly as


if an explicit argument check had been coded.

detail. In either case, the runtime exception is thrown as the result of the value
passed and should be so documented. In fact, I would argue that it is actually
more important to document a runtime exception thrown as the result of a sys-
tem-induced argument check than one that is explicitly thrown (which at the very
least can be readily seen looking at the source code).

1.6.3.2 On const References


I recently read all 100+ pages of Bug Id (or rather RFE) 4211070 entitled “Java
should support const parameters (like C++) for code maintenance.” This will
simply never happen. Despite repeated assertions to the contrary in comments
at the bottom of this RFE, immutable objects are in large part the Java program-
ming language answer to const references (i.e., references that cannot be
used to change the state of an object). No number of dogged assertions to the
contrary will change this fact. Proper encapsulation (such as not automatically
declaring set methods for all of your private instance variables) is your first
line of defense. Failing that, however, you must use an immutable object. The
term immutable object, however, is grossly misleading in this context. It is

Immutable types (not objects) are how const references are imple-
mented in the Java programming language.

actually an immutable type that is required. The immutable type may be either a
class or interface type, but in either case does not include any mutator meth-

FIELDS AND METHODS 155


ods that can change the state of the object referenced. This implies the pos-
sibility that the class of objects referenced may or may not be immutable.
There is actually another option. The @param tag can include a guarantee
not to modify the object passed. That of course would also imply that the refer-
ence is not leaked outside of the method or constructor to which it is passed.
Such a guarantee means that client programmers do not have to make a defen-
sive copy of the object passed. There is an example in 6.2 Assertions of how to
implement the interface contract equivalent of a const reference inexpensively
using assertions.
The main objection to using immutable types as const references in the
comments at the bottom of RFE 4211070 is what I would describe as an instinc-
tual feeling that having both a mutable and immutable version of a class is not
proper object-oriented design. I could not agree more. This began with
String and StringBuffer (which was an extraordinary language design
problem) and has been formalized in a number of very respected books includ-
ing the example of a banking account in Doug Lea’s Concurrent Programming
in Java.97 Two classes are used when the object’s natural interface is indeed
mutable. For example, the natural interface for a banking account is mutable.
Passing an const-like (read safe) reference to a banking account requires a
separate, immutable type that does not include any mutator methods. The prob-
lem comes when the immutable type is declared in such a way that it becomes
the equal of the natural interface, if not more prominent. Nothing, not even a love
affair with design patterns, should be allowed to ride roughshod over good
object-oriented design. The harsh criticisms found in the comments at the bot-
tom of RFE 4211070 are warranted in this regard.
The middle ground is to de-emphasize the immutable type by making it an
inner class. This has the immediate advantage of full access to the private
members of the mutable class. More importantly, the class hierarchy is not dis-
torted. I will use Doug Lea’s banking account an example. As I look at Lea’s
ImmutableAccount class, the delegate field just seems to jump off the
page and say “I’m a link variable.” Here is a reworked version of that example:

class Account {

156 JAVA RULES


private long balance;

public Account(long initialDeposit) {


balance = initialDeposit;
}

public synchronized long balance() {


return balance;
}

public synchronized void debit(long amount)


throws InsufficientFundsException {
credit(-amount);
}
public synchronized void credit(long amount)
throws InsufficientFundsException {
if (amount >= 0 || balance >= -amount)
balance += amount;
else
throw new InsufficientFundsException();
}

private ImmutableAccount immutable;

public ImmutableAccount immutable() {


if (immutable == null)
return immutable = new Immutable();
return immutable;
}

private final class Immutable implements ImmutableAccount {


public synchronized long balance() {
return balance;
}
}
}

The code in bold is mine. Other than some gratuitous name changes, the rest is
from Concurrent Programming in Java.97 Immutable is an inner class so

97. Doug Lea, Concurrent Programming in Java, Second Edition, (Boston, Addison-Wesley,
2002), §2.4.3, “Read-Only Adapters.” It is difficult for me to image anyone not owning Lea’s book,
so I will let you read his (rather entertaining) explanation as to why recorder should not be
passed an instance of the mutable class.

FIELDS AND METHODS 157


that is can reference private instance variables in Account such as
balance . Nested types declared in a non-public class such as Account
are inaccessible outside of the package in which they are declared (regardless
of their access modifier). Consequently a public interface must be used as
the immutable type. The interface type returned by the immutable()
method is declared as follows.

public interface ImmutableAccount {


long balance();
}

Note that Account can still be an interface rather than a class type. I just
wanted to simplify the example as much as possible. The required changes to
the other two classes in Doug Lea’s example are very minor, and are also
marked in bold:

class AccountRecorder { // A logging facility


public void recordBalance(ImmutableAccount a) {
System.out.println(a.balance()); //or record in file
}
}
class AccountHolder {
private Account account = new Account(0);
private AccountRecorder recorder;

public AccountHolder(AccountRecorder r) {
recorder = r;
}

public synchronized void acceptMoney(long amount) {


try {
account.credit(amount);
recorder.recordBalance(account.immutable());
}
catch (InsufficientFundsException e) {
System.out.println("Cannot accept negative amount.");
}
}
}97

158 JAVA RULES


I can almost imagine him shaking his head and asking: “What’s the difference?”
The reason why I like this solution is that if you try to achieve a const-like refer-
ence by splitting the implementation, the immutable type always ends up being
the supertype. If having two classes feels instinctively wrong, making the immu-
table type the one that is properly named (such as the Account interface in
Lea’s example) only flames the fires of discontent. Using an inner class to return
a reference to what is essentially an immutable subinterface does not distort
the class hierarchy. I refer to this as the Immutable Inner design pattern.
Would-Be Mutators and Immutable Inners and are very closely related. Both
provide const-like references to mutable objects. As described in 5.2 Would-
Be Mutator Methods in Volume 1, Would-Be Mutators do not actually modify the
target object. They return a copy of what the target object would look like were
it modified by an actual mutator method. Thus what appears to be a mutable
class of objects is actually immutable. Immutable Inners accomplish essentially
the same thing by passing an immutable subinterface to untrusted clients. How
do you decide which to use? The standard answer to this question is to start with
Would-Be Mutators and switch to an Immutable Inner only if there is a perfor-
mance bottleneck related to making too many defensive copies of a large
object. In other words, Immutable Inners are the extreme solution. They are best
used for large objects that are expensive to copy. Doug Lea also points out that
Would-Be Mutators cannot be used for “objects that maintain references to files,
threads, or other resources that should not themselves be copied.”98

NOTE 1.3
Being a thorough Bloch devotee, it took me a while to realize that he
confuses Would-Be Mutators and making defensive copies in Effective
Java. In “Item 13: Favor immutability” he talks about making defensive
copies (of the values stored in instance variables) while actually copying
the current object. Returning a copy of the current object is the Would-
Be Mutator design pattern. The motivation for the Would-Be Muta-

98. Doug Lea, Concurrent Programming in Java, §2.4.3, Read-Only Adapters.”

FIELDS AND METHODS 159


tor design pattern is to eliminate the need for synchronization
(by making a class immutable), whereas making defensive cop-
ies is basic to encapsulation. The two should not be confused. On
a different subject, if you have not done so already, it is a good idea to
read 1.5.3 Declaring Mutable Objects final before continuing.

1.6.3.3 Making Defensive Copies


Access in the Java programming language is an all-or-nothing proposition.
Unless declared final, client programmers who have access to a field have
both read and write privileges. There is no read-only access. Using the final
keyword to implement read-only access does not work for two reasons. For
primitive data types you run into the problem of changeable inlined constants
discussed in 1.5.2.1 The Problem of Changeable Inlined Constants. For refer-
ence types, final only means a different reference cannot be assigned to the
variable. If the object referenced is mutable, the reference stored in that vari-
ables can still be used to invoke mutator methods, which in this context is the
same as having write privileges. Thus the JLS includes the following advice.
If the read-only nature of final is required, a better choice is to
declare a private static variable and a suitable accessor method
to get its value. Thus we recommend:

private static int N;


public static int getN() { return N; }

rather than:

public static final int N = …;

[end of quote] 99

This passage from the JLS contributes to a popular myth among Java program-
mers that read-only access can be implemented by using private fields and

99. Gosling et al., §13.4.8, “final Fields and Constants.”

160 JAVA RULES


public accessor method.100 Not true! That only works for primitive data
types and immutable objects.
Read-only access for mutable objects requires a much higher degree encap-
sulation in which the responsible programmer must guarantee that there is only
one reference to the encapsulated object. Doing so requires making what are
referred to as defensive copies. What you are “defending” is the mutable
object by making sure no one else has a reference to it. It is that simple. There
are three cases to consider:
• Being passed a reference to a mutable object: Immutable objects are
passed into a class either as arguments in either a method invocation or
class instance creation expression. In other words, what we are talking
about here is making a defensive copy of a mutable object that is refer-
enced either by a method or constructor parameter. The rationale for doing
so is exactly the same in both cases
• Using a mutable object as an argument in a method invocation or class
instance creation expression
• Returning a reference to a mutable object
The last two are essentially the same in that a reference to a mutable object is
being passed outside of the of the class.
In general, you should always assume that a mutable object will be modified
after it is passed to your method or constructor. In other words, do not trust
client programmers, especially if the method or constructor is public or
protected. Bloch addresses the question of trust in “Trust versus Being
Defensive” in a BIll Venner interview at www.artima.com/intv/blochP.html.
His comments in that interview affirm that making defensive copies is basic to
encapsulation. It should not be considered an option. Sometimes there is a very
high probability that you are being passed the only reference to an object (as is
always the case if the object is created in an argument list), but unless that is
backed by an interface contract, I would not rely on the fact.

100.String objects are sometimes called read-only, but this is an abuse of terminology. Vari-
ables are read-only. Objects are either mutable or immutable.

FIELDS AND METHODS 161


There are two very important details to observe when making defense cop-
ies. The first is what Bloch calls the window of vulnerability when making
defensive copies in a multi-threaded application. The following example of a
Period constructor that is passed to (mutable) Date objects is from a talk he
gave at a JavaOne conference.101

// BROKEN - Permits multithreaded attack!


public Period( Date start, Date end) {
if (start.compareTo( end) > 0)
throw new IllegalArgumentException( start + ">" + end);
// Window of vulnerability
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
}

Here is the corrected constructor from the same Bloch document:

// Repaired constructor - defensively copies parameters


public Period( Date start, Date end) {
this.start = new Date( start.getTime());
this.end = new Date( end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException( start + ">" + end);
}

Note that the argument check now uses instance variables (the defense copies)
and not the method parameters. This eliminates the possibility that whoever cre-
ates a Period object either maliciously or because the Period class is
documented to make defensive copies immediately changes the Date
objects passed. In a multithreaded application, it is possible for such a change
to occur after the argument check and before the defensive copies are made. In
other words, during the window of vulnerability. The consequence of such a
change is twofold. First the dates passed are not what the client programmer
intended (unless done maliciously) and secondly the start > end class invari-
ant is compromised. You should make it a practice to always code like this, even
if the class is initially only used in single threaded applications.

101.Joshua Bloch, “More Effective Programming With Java™ Technology” delivered at the 2002
JavaOne conference (Session 2502).

162 JAVA RULES


The other important detail about making defense copies involves a measure
of subtlety. Consider the follow mutator methods from the same Bloch example.

// Repaired accessors - defensively copy fields


public Date start() {
return (Date) start.clone(); // (clone OK)
}
public Date end() {
return (Date) end.clone(); // " "
}

Why is using the clone() method okay when making defensive copies
in accessor methods, but not in mutator methods or constructors? Under-
standing the answer to this question is critical to proper encapsulation. The dif-
ference is that in mutator methods and constructors you are being passed an
object the purports to be a Date object but may in fact be a subclass of Date.
If the object is in fact a subclass of Date, the clone() method in the subclass
can be used to store references to the object cloned. For example,

import java.util.*;
class Test {
public static void main(String[] args) {
Period period = new Period(new MaliciousDate(),
new MaliciousDate());
Date start = period.start();
Date end = period.end();
System.out.println("start = " + start);
System.out.println(" end = " + end);
MaliciousDate.attack();
System.out.println("start = " + start);
System.out.println(" end = " + end);
}
}
class Period {
Date start, end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(start + ">" + end);
// Window of vulnerability
this.start = (Date) start.clone(); //big mistake
this.end = (Date) end.clone(); // ~ditto~
}

FIELDS AND METHODS 163


public Date start() {
return (Date) start.clone(); // (clone OK)
}
public Date end() {
return (Date) end.clone(); // " "
}
}

class MaliciousDate extends Date {


static List dates = new ArrayList();
public Object clone() {
//record the date
Date date = (Date)super.clone();
dates.add(date);
return date;
}
static void attack() {
Iterator i = dates.iterator();
while (i.hasNext()) {
Date date = (Date)i.next();
date.setYear(-100);
}
}
}

Executing this program prints:

start = Fri Jan 10 11:55:14 EST 2003


end = Fri Jan 10 11:55:14 EST 2003
start = Fri Jan 10 11:55:14 EST 1800
end = Fri Jan 10 11:55:14 EST 1800

Unlike the clone() method in a malicious subclass, the clone() method in


Date can be trusted not to store references to the object cloned. Thus after the
Date has entered the class and been defensively copied using a copy con-
structor, it is safe to use clone() to copy the object before passing it back
out of the class either as an argument or in a return statement. Why? Because

Copy constructors should be consider a fundamental part of any


public mutable class because they are essential to encapsulation.

164 JAVA RULES


the class of the object is known after making the initial defensive copy. In
this example, the class in known to be Date, so there is no longer the possibility
of a subclass implementation of the clone() method being substituted at run
time.
The rule of thumb is to never use the clone() method to make defensive
copies when passed a reference to a mutable object unless the Cloneable
class is either final (which includes the dynamically created array classes) or
package-private. Otherwise, devious subclasses can override the clone()
method and store a reference to the newly created object, effectively defeating
your encapsulation.
Inasmuch as making defensive copies is basic to encapsulation, you should
always document the fact that “a defensive copy is made” so that client
programmers understand they are free to modify the object after it is
passed.
Copying the mutable object when it is passed to your class is only half the
job. The other half is making sure that you do not inadvertently pass a reference
to the object out of your class. This can happen whenever a method or construc-

Returning a reference to a mutable instance variable or passing it to


another method or constructor breaks encapsulation.

tor is invoked. Never use the name of a mutable instance variable as an


argument in a method invocation or class instance creation expression.
The same is true of return statements.
All this makes encapsulating a mutable object much more difficult than prim-
itive data types or immutable objects. In short, mutable objects are expensive.
The requirement to make defensive copies of mutable objects is just one of
many reasons why immutable classes are so desirable.

1.6.4 The throws Clause


The fact that a method throws an exception is an important part of the interface
contract that is usually referred to as the exception specification. This
includes runtime exceptions and errors as well as checked exceptions. Thus the

FIELDS AND METHODS 165


throws clause and the @throws tag must be discussed together. They are
both a part of the exception specification. As stated in the “How to Write Doc
Comments for the Javadoc Tool” document:
Documenting Exceptions in API Specs

The API specification for methods is a contract between a caller and an


implementor. Javadoc-generated API documentation contains two ways
of specifying this contract for exceptions -- the “throws” clause in the
declaration, and the @throws Javadoc tag….102

It is important to understand why the @throws has the same weight as the
throws clause in establishing an exception specification.
The JLS says that “it is permitted but not required to mention other
(unchecked) exceptions in a throws clause.”103 This specification is seriously
outdated and should have been changed in the Second Edition of the JLS. It is
indicative of a subtle and rarely discussed change in how exceptions are docu-
mented on the Java platform. In the First Edition of the JLS (which unlike the Sec-
ond Edition included the API docs for the java.lang, java.util, and
java.io packages) runtime exceptions were included in the throws clause
of method declarations. This practice in now openly discouraged:
By convention, unchecked exceptions should not be included in a
throws clause. (Including them is considered to be poor programming
practice. The compiler treats them as comments, and does no check-
ing on them.)104

The only exception to this rule that unchecked exceptions should not be included
in throws clauses are runtime exceptions that should have been declared as
checked exceptions. In the core API this includes MissingResource-

102. Unascribed, “How to Write Doc Comments for the Javadoc Tool” at java.sun.com/j2se/
javadoc/writingdoccomments/index.html#throwstag, (Mountain View: Sun Microsys-
tems, Inc., 2000), “Documenting Exceptions with @throws Tag.”
103. Gosling et al., §8.4.4, “Method Throws.”
104. Unascribed, “How to Write Doc Comments for the Javadoc Tool” at java.sun.com/j2se/
javadoc/writingdoccomments/index.html#throwstag, “Documenting Exceptions with
@throws Tag.” Bug Id 4349458, “Runtime exceptions should not be included in throws clause” is
interesting in this regard. It concerns the javax.microedition package (J2ME) and com-
plains that runtime exceptions in throws clauses bloat class files.

166 JAVA RULES


Exception, SecurityException, and NumberFormatException.
See 6.5 The Throwable Class Hierarchy for a discussion.
The problem with including unchecked exceptions in the throws clause is
that the naming convention for exceptions is such that there is no way to differ-
entiate runtime exceptions from checked exceptions. Including runtime excep-
tions in a throws clause is therefore confusing because it suggests that they
are checked exceptions. The above quotes from the First Edition of the JLS and

Never include runtime exceptions or errors in the throws clause of


a method or constructor header.

from the “How to Write Doc Comments for the Javadoc Tool” document (written
some five years latter) reflect a subtle change that de-emphasized the throws
clause as a means of documenting exceptions. The throws clause is now best
thought of as primarily used by compilers to check for exception handlers. The
@throws tag should be used to “document” exceptions (versus merely “declar-
ing” that they are thrown). There is some redundancy in this design in that
checked exceptions are both documented using the @throws tag and declared
in the throws clause, but the redundancy is not without purpose. Unlike the
throws clause, the reason(s) why an exception is thrown can only be
explained using an @throws tag.
The checked exceptions either explicitly thrown or propagated in a method
or constructor body only need to be assignable to one of the exception types in
the throws clause. As stated in the JLS:
For each checked exception which is a possible result, the throws
clause for the method or constructor must mention the class of that
exception or one of the superclasses of the class of that exception.105

There are only two cases, however, in which an exception superclass should be
named in the throws clause: high-level exceptions as defined in 6.5.1 General
Exception Classes and umbrella exceptions. The documentation requirements

105. 11.2 Compile-Time Checking of Exceptions

FIELDS AND METHODS 167


for umbrella exceptions are discussed at the very bottom of 6.5.1.1 Umbrella
Exceptions.
Documenting methods and constructors that throw high-level exceptions is
problematic because client programmers may be interested in handling some of
the low-level exceptions that “cause” a high-level exception. If client program-
mers are interested in handling low-level exceptions, those low-level
exceptions must be documented. The first problem is deciding which low-
level exceptions to document. Here it is safe to follow the rule of “if in doubt,
leave it out.” Once the decision is made to document a low-level decision, there
are two possibilities: either it is a subclass of the high-level exception named in
the throws clause or else exception translation was used. If it is a subclass,
then all that needs to be done is to add a separate @throws tag to the method
or constructor.106 Client programmers can explicitly catch the subclass. How do
client programmers catch low-level exceptions that are translated? They do not
(at least not explicitly), but as of the 1.4 release than can query the cause of a
high-level exception by invoking the getCause() method in Throwable.
Adding a separate @throws tag would therefore be a gross mistake because it
would suggest that the exception can be explicitly caught (which may or may not
lead to compiler errors in client code depending on whether or not the exception
in question is checked). The only answer then is to use the @throws tag for
the high-level exception to suggest that client programmers may want to
handle a specific low-level exception, but that doing so requires invoking
the getCause() method in Throwable to determine the cause of the
high-level exception.
The exception specification in a throws clause is binding even if the body of
the method or constructor does not actually throw the exception. For example,
class Test {
public static void main(String[] args) {
test();
}

106. The compiler will allow related classes to be named in a throws clause. For example,
throws IOException, FileNotFoundException compiles, but no ones codes like this.
If an exception superclass is used in the throws clause, subclasses are documented using the
@throws tag.

168 JAVA RULES


static void test() throws Exception {}
}

Attempting to compile this program generates the following error:

Test.java:3: unreported exception java.lang.Exception; must be


caught or declared to be thrown
test();
^
1 error

This compiler behavior makes it possible to establish an exception specification


for interface methods (as well as other abstract methods). It also comes in
handy for methods that throw an UnsupportedOperationException.
What happens if you forget to include a checked exception in the throws
clause? That is what checked exceptions are all about. The compiler generates
an error message to remind you. For example,

class Test {
public static void main(String[] args) {
throw new Exception();
}
}

Attempting to compile this program generate the following compiler error:

Test.java:3: unreported exception java.lang.Exception; must be


caught or declared to be thrown
throw new Exception();
^
1 error

This is essentially what “checked” exception means; the compiler “checks” for
an exception handler. If there is none, the method or constructor must declare
that the exception is thrown.
All checked exceptions should be documented using the @throws
tag. There is general agreement on this point. Opinions as to which unchecked
exceptions should be documented vary. Some software engineers and technical
writers think that all of the unchecked exceptions should be documented. For
example, in “Item 44: Document all exceptions thrown by each method” Bloch

FIELDS AND METHODS 169


says “documenting all of the unchecked exceptions that each method can throw
is an ideal.”107 As Bloch himself points out, however, this is an impossible ideal.
Unless every method invocation or class instance creation expression is coded
in a try block that has a catch(Throwable e) catchall exception handler,
there is no guarantee that a method or constructor will not propagate an
unchecked exception thrown by some other method or constructor. Further-
more, a LinkageError or VirtualMachineError can occur in almost
every method. (That is why they are called “every method errors” in Chapter 6,
“Assertions, Exceptions, and Logging.”) As another example of the expectation
that methods and constructors should document all of the unchecked excep-
tions they throw, there is at least one bug report that suggests throwing an
undocumented exception or error is a contract violation. 108 I simply cannot
agree with the blanket statement that all unchecked exceptions (particularly
errors) should be documented.
The “How to Write Doc Comments for the Javadoc Tool” document
addresses this question. Though not an official specification, it must be
regarded as authoritative because the section entitled “Documenting Exceptions
with @throws Tag” is the most extensive discussion of this subject available
from Sun. It states that the @throws tag should be used for “any unchecked
exceptions that the caller might reasonably want to catch, with the exception of
NullPointerException . Errors should not be documented as they are
unpredictable.” 109 Note that “unchecked exceptions” in this context is evidently a
reference to runtime exceptions; otherwise the last sentence would not make
any sense. Elsewhere in the same document a section entitled “Documenting
Unchecked Exceptions” begins as follows.
It is generally desirable to document the unchecked exceptions that a
method can throw: this allows (but does not require) the caller to han-
dle these exceptions. For example, it allows the caller to “translate” an

107. Bloch, Effective Java.


108. See Bug Id 4129911.
109. Unascribed, “How to Write Doc Comments for the Javadoc Tool,” (Mountain View: Sun Micro-
systems, Inc., 2000), java.sun.com/j2se/javadoc/writingdoccomments/
index.html#tag .

170 JAVA RULES


implementation-dependent unchecked exception to some other excep-
tion that is more appropriate to the caller's exported abstraction.110

This advice is totally bogus. Generally speaking, runtime exceptions are not
caught. They are programmer errors (or bugs) that are fixed during development
and unit testing. Furthermore, even if they are caught, it would most likely be in a
catchall exception handler such as catch(Throwable e), not explicitly as
this specification implies. The reason for documenting runtime exceptions
is that they are preconditions. If the reason why they are thrown is not docu-
mented, programmers will have to revert to reading source code to find out why
their classes are not compiling.
As explained in 6.5 The Throwable Class Hierarchy, runtime exceptions
that should have been declared as checked exceptions (but were not because
there is no general agreement on the meaning of the term runtime exception)
should be documented as if they actually were checked exceptions. There are
three such runtime exceptions in the core API. They are MissingResource-
Exception, SecurityException, and NumberFormatException.
These runtime exceptions and other like them should not only be documented
using the @throws tag, but should also be declared in throws clauses.
Errors are documented at the programmer’s discretion. There are some rea-
sonable guidelines that can be formulated, however. As explained in 6.5 The
Throwable Class Hierarchy, errors are either “every method errors,” “non-
occurring errors,” or “recurring end-user errors.” The only every method error
that would ever be documented is OutOfMemoryError. There are only two
examples of this in the entire J2SE (including support packages) of which I am
aware, so you can be assured that it is rarely done. Recurring end-user errors
should always be documented using the @throws tag (to encourage client pro-
grammer to catch them in top-level exception handlers). That only leaves non-
occurring errors, the overwhelming majority of which are either assertion errors
or system configuration errors. Assertion errors should never be documented.
The “programmer’s discretion” then is limited to the documentation of system

110. Unascribed, “How to Write Doc Comments for the Javadoc Tool,” java.sun.com/j2se/
javadoc/writingdoccomments/index.html#throwstag.

FIELDS AND METHODS 171


configuration errors. I am indifferent on this point because documenting sys-
tem configuration errors seems informative, but generally speaking they
are closely tied to the implementation of a method or constructor. This
becomes an encapsulation problem. As stated in the “How to Write Doc Com-
ments for the Javadoc Tool” document:
Note that it is always inappropriate to document that a method throws
an unchecked exception that is tied to the current implementation of
that method. In other words, document exceptions that are indepen-
dent of the underlying implementation. For example, a method that
takes an index and uses an array internally should not be documented
to throw an ArrayIndexOutOfBoundsException, as another
implementation could use a data structure other than an array inter-
nally. It is, however, generally appropriate to document that such a
method throws an IndexOutOfBoundsException.111

I really do not like this example. It is misleading because most runtime excep-
tions are direct subclasses of RuntimeException and do not have any sub-
classes. In fact, ignoring the org.omg package, the only other runtime
exceptions in the core API that do have subclasses are IllegalArgument-
Exception , IllegalStateException, and UnsupportedOpera-
tionException . When a subclass of one of these runtime exceptions is
thrown it (versus the superclass) is always documented in the @throws tag. In
fact, the only time I have seen this example put into practice is in the String
class, which is funny because if the methods in the String class are not going
to throw StringIndexOutOfBoundsException who is? Nevertheless,
care should be taken not to document unchecked exceptions that may not be
thrown in a different implementation of the same method.
Encapsulation issues aside, documenting system configuration errors may
encourage client programmers to write exception handlers that translate them
into some high-level exception. How useful is that if these are non-occurring
errors? Explicitly catching a non-occurring error is like “Waiting for Gadot.” A

111. Ibid.

172 JAVA RULES


more informed and technically sound approach to this problem is to use a catch-
all exception handler as discussed in 6.8.1.1 Catchall Exception Handlers.
What about NullPointerException (often written as NPE)? A quote
from the “How to Write Doc Comments for the Javadoc Tool” document above
say that NullPointerException should not be documented. This is con-
trary to actual practice, however. For example,
Parameters:
newLocale - the new default locale
Throws:
NullPointerException - if newLocale is null

These documentation comments are from the setDefault(Locale new-


Locale) in the Locale class. Why the discrepancy? It may be that throwing
NullPointerException is sometimes documented at the class level
rather than in individual methods and constructors, but this should only be done
when it is consistently thrown throughout the entire class (which requires a great
deal of careful checking). Otherwise you run into problems as did the following
specification from java.util.logging package.
Null Pointers

In general, unless otherwise noted in the javadoc, methods and con-


tructors [sic] will throw NullPointerException if passed a
null argument. The one broad exception to this rule is that the log-
ging convenience methods in the Logger class (the log, logp, log,
severe, warning, config, fine, finer, and finest methods) will accept
null values for all arguments except for the initial Level argument
(if any).112

There is a rare example of documenting NullPointerException at the


package level. Think twice before doing this, however. This unprecedented docu-
menting of NullPointerException at the package level resulted in a bar-
rage of bug reports.113

112. API docs for the java.util.logging package. See also Bug Id 4478366.

FIELDS AND METHODS 173


1.7 Local Variables
A variable declared in a block is said to be local to that block, or a local vari-
able. Local variables share a common syntax with fields. The main difference is
that final is the only modifier that can be used in the declaration of a local vari-
able. See 1.5.6 Declaring Local Variables and Parameters final for a discus-
sion.
The rules of definite assignment require that a local variable be explicitly
initialized before it is used. If a local variable declaration statement does not
include a variable initializer, then the local variable must be assigned a value
before it can be used. Definite assignment is discussed in the next section.
The following advice is a well-established practice in structured program-
ming. One that has been followed for many years in other programming lan-
guages.

Local variables should always be declared immediately before their


first use.

Doing so makes methods much easier to read because code does not have to
be searched to determine the initial value of a local variable or if it is used else-
where in the method, constructor, or initialization block. Bloch argues essentially
the same thing in “Item 29: Minimize the scope of local variables”, so why does
the “Code Conventions for the Java Programming Language” document include
the following?
Put declarations only at the beginning of blocks. (A block is any code
surrounded by curly braces “{” and “}”.) Don't wait to declare variables
until their first use; it can confuse the unwary programmer and hamper
code portability within the scope.

113. The main Bud Id is 4486754, “LOGGING APIs: Expected NullPointerException isn't
thrown” in which more than a few methods and constructors in the logging API are faulted for not
actually throwing the exception. Other such bugs include Bug Id 4625722,
“java.util.logging.Level(null, int[, String]) (sic) doesn't throw NPE” and Bug
Id 4635308, “ java.util.logging null reaction doc should be updated,” and Bug Id
4398380, “Logging APIs: SocketHandler constructors spec need clarification.”

174 JAVA RULES


void myMethod() {
int int1 = 0; // beginning of method block

if (condition) {
int int2 = 0; // beginning of "if" block
...
}
}114

The answer is that this specification is just plain wrong. In fact, the statement
that the placement of local variable declarations “may hamper code portability
within the scope” is a little bizarre. If Sun is going to continue to promote this
document it should be revised to reflect current coding practices.
The remainder of this section discusses declaring local variables in a loop.
For example,

for (int i=0; i<100; i++) {


String s = Integer.toString(i);
}

Is s allocated over and over again? The short answer is No, but it is important to
understand why because otherwise there is a tendency to think that local vari-
able declaration statements inside a loop are somehow inefficient. That simply is
not the case.
Local variables and parameters are allocated on the stack in the same local
variable array as this. Unlike Java arrays, the components in a local variable
array do not all have the same component type. The following is a complete list
of variables allocated in a local variable array:
• The this reference (in local variable zero)
• Method parameters
• Constructor parameters
• Exception-handler parameters
• Local variables

114. Unascribed, “Code Conventions for the Java Programming Language,” (Mountain View: Sun
Microsystems, 1995-1999), §6.3, “Placement.”

FIELDS AND METHODS 175


None of these variables have names at run time. They are merely components
(nameless variables) in the local variable array. Machine instructions refer to
them using only an index value. For example,

class Test {
void test() {
/*
* The astore_<n> instructions are used to pop a reference
* off of the stack and store it in the local variable array.
* The <n> is the index value. In an instance method such as
* this, "local variable zero" is always a reference to the
* current object (a.k.a. this).
*/
String mathew = "Mathew"; //astore_1
String mark = "Mark"; //astore_2
String luke = "Luke"; //astore_3
String john = "John"; //astore_4
Object Paul = this; //astore_5
}
}

The decompiled code for the test() method is as follows:

Method void test()


0 ldc #2 <String "Mathew">
2 astore_1
3 ldc #3 <String "Mark">
5 astore_2
6 ldc #4 <String "Luke">
8 astore_3
9 ldc #5 <String "John">
11 astore 4
13 aload_0
14 astore 5
16 return

Notice the aload_0 instruction, which is a reference to local variable zero.


The compiler computes the maximum size of the local variable array at any
given time during the execution of a method (which in the last example is six).
The maximum size of the local variable array is then stored as max_locals in
the Code attribute of a method, and is used by the JVM when allocating memory
for a frame on the stack. Although the size of the local variable array is always

176 JAVA RULES


max_locals, how much of the local variable array is in use at any given time
depends on how many local variables are in scope. For example,

class Test {
void test() {
if (true) {
String mathew = "Mathew";
String mark = "Mark";
String luke = "Luke";
String john = "John";
Object Paul = this;
}
String Judas = "Judas";
}
}

The decompiled code for the test() method is as follows:

Method void test()


0 ldc #2 <String "Mathew">
2 astore_1
3 ldc #3 <String "Mark">
5 astore_2
6 ldc #4 <String "Luke">
8 astore_3
9 ldc #5 <String "John">
11 astore 4
13 aload_0
14 astore 5
16 ldc #6 <String "Judas">
18 astore_1
19 return

The second istore_1 instruction in bold is reusing the second component of


the local variable array because Mathew, Mark, Luke, and John are no
longer in scope. (The scope of a local variable is discussed in the next chapter.)
Local variables and parameters are thus not really created or destroyed as
are fields and array components. The key to understanding all of this is that until
a local variable passes out of scope, the same component of the local
variable array is used over and over again. If you understand that, then you
understand what is meant when it is said that a local variable declaration

FIELDS AND METHODS 177


statement has the semantics of an assignment statement. The type of the
local variable is really only used by the compiler. It determines which machine
instruction to use, such as istore_<n> for an integer or astore_<n> for a
reference type. As already stated, the name of the local variable is not used at
all. That only leaves the variable initializer, which is in fact evaluated every time
the local variable declaration statement is executed. That is why they are called
local variable declaration statements in the first place; they are executable (not
unlike assignment statements). Thus there is no performance penalty what-
soever for declaring local variables in a loop. Doing so makes simply
makes them local to the loop, which they should be if they are not used outside
of the loop.
Now can you tell which of the following examples is more efficient?

//FIRST EXAMPLE
int defined = 0;
char ch;
for (int i=0; i<=Character.MAX_VALUE; i++) {
ch = (char)(i);
if (Character.isDefined(ch))
defined++;
}
System.out.println(defined + " character codes are defined");

//SECOND EXAMPLE
int defined = 0;
for (int i=0; i<=Character.MAX_VALUE; i++) {
char ch = (char)(i);
if (Character.isDefined(ch))
defined++;
}
System.out.println(defined + " character codes are defined");

The answer is neither. The bytecodes for these two examples are identical except
for the fact that ch and i have swapped places in the local variable array. The
only real difference is the scope of ch. In the first example, by declaring ch out-
side of the loop you make it impossible to reuse ch as a data name in the remain-
der of the method or constructor body.

178 JAVA RULES


I like to think of local variables that are only used in a loop as intermediate
results. Ideally you should always use more complex field access or method

Avoid the temptation of declaring intermediate results outside of a


loop. Doing so looks natural enough, but betrays an ignorance of
how local variable arrays are used.

invocation expressions that directly use the value of an intermediate result. For
example,

int defined = 0;
for (int i=0; i<=Character.MAX_VALUE; i++) {
if (Character.isDefined((char)(i)))
defined++;
}
System.out.println(defined + " character codes are defined");

This is not always possible, however, because the resultant expression may be
too long to fit on a single line. For example,

int hashCollisions = 0;
for (int i=0; i<=Character.MAX_VALUE; i++) {
int hashCode = (new Character((char)i)).hashCode();
if ((hashCode & 0x7FFFFFFF) % size == index)
hashCollisions++;
}

In this example, hashCode is an intermediate result. It must be computed on a


separate line, however, for the sake of readability. Looking at this code you may
be inclined to think the local variable hashCode is created over and over again,
but that simply is not the case.

1.8 “Write Once, Compile Anywhere”


The subjects of definite assignment and unreachable statements discussed in
the following subsections may at first appear to be unrelated. On closer exami-
nation, however, they have much in common. That commonality is expressed in
the catchphrase “Write Once, Compile Anywhere” (which signifies the standard-
ization of Java compilers). Everyone knows about “Write Once, Run Anywhere,”

FIELDS AND METHODS 179


but little attention is paid to “Write Once, Compile Anywhere.” In both cases, Java
technology is ground breaking. By that I mean there is no precedent for the rele-
vant chapter and section in the JLS that expound the rules of definite assignment
and unreachable statements. One of the reasons I discuss these subjects
together is that while most of the JLS can be read and appreciated by a general
audience, Chapter 16 Definite Assignment and section 14.20 Unreachable State-
ments are clearly intended for those select few programmers who make their liv-
ing in the arcane world of compilers. Even the simplest of these rules are difficult
to read. For example, a local variable is “definitely assigned before a catch
block [if and only if] it is definitely assigned before the try block.”115 (In other
words, local variables are never definitely assigned in a try block).
Correcting the resultant compiler errors is usually a simple matter of rear-
ranging a few lines of code. Instead of spending an untold number of hours in a
vain attempt to learn and memorize all of the convoluted rules in the JLS, appli-
cation programmers must wait until a definite assignment compiler error (one
that says a local variable “…might not have been initialized”) or an
“unreachable statement” compiler error is generated and then learn from
experience. Sometimes it may be clear that you made a mistake, and other
times you may think the compiler is wrong. In either case, the only thing to do is
to change the code in such a way that the “conservative flow analysis”116 per-
formed by a Java compiler will conclude that the local variable has been defi-
nitely assigned or that the statement is reachable.
The following subsections serve only to introduce the reader to the rules of
definite assignment and unreachable statements. These rules apply to methods,
constructors, and initialization blocks. They are by no means a complete discus-
sion of the relevant chapter and section in the JLS, and include only a limited
number of examples.

115. Gosling et al., The Java Language Specification, §16.2.14, “try Statements.”
116. Gosling et al., The Java Language Specification, introduction to Chapter 16, “Definite
Assignment” and also 14.20, “Unreachable Statements.”

180 JAVA RULES


1.8.1 Definite Assignment
All variables must be initialized before they can be used. Java automatically ini-
tializes fields (which includes the components of an array) with standard default
values and parameters are initialized with the value of their corresponding argu-
ments, but the value of a local variable must be explicitly initialized before it can
be used. The rules that govern the initialization of local variables are referred to
as definite assignment. The same rules are used to make sure that blank finals
are properly initialized. Thus the specification for blank finals and definite
assignment are one and the same. As explained in the JLS:
Each local variable and every blank final field must have a defi-
nitely assigned value when any access of its value occurs. A Java
compiler must carry out a specific conservative flow analysis to make
sure that, for every access of a local variable or blank final field f,
f is definitely assigned before the access; otherwise a compile-time
error must occur.

Similarly, every blank final variable must be assigned at most once;


it must be definitely unassigned when an assignment to it occurs. A
Java compiler must carry out a specific conservative flow analysis to
make sure that, for every assignment to a blank final variable, the
variable is definitely unassigned before the assignment; otherwise a
compile-time error must occur.…

The idea behind definite assignment is that an assignment to the local


variable or blank final field must occur on every possible execution
path to the access. Similarly, the idea behind definite unassignment is
that no other assignment to the blank final variable is permitted to
occur on any possible execution path to an assignment…

Here is a very simple example from the core API of what the JLS means by
“every possible execution path:”

Signal signal;
if (name.startsWith("CTRL"))
signal = new Signal(name);
else
signal = new Signal(name.substring(3));

FIELDS AND METHODS 181


The local variable signal is “definitely” assigned in this example because there
are two execution paths and it is assigned on both.
The “assignment” in definite assignment always refers to an assignment
statement. Furthermore, the operator must be a simple assignment operator.
For example, the following local variable declaration implies that the first use of
the variable x is as the left-hand operand of an assignment expression:

int x;

Only the simple assignment operator can be used to initialize a local variable
because the compound assignment operators += -= *= /= &= |= ^= %=
<<= >>= and >>>= first use the left-hand operand in the compounded opera-
tion, and then assign the result to the variable.
From the point at which a local variable is declared until it is definitely
assigned, any use of the variable is a compiler error. For example,

class Test {
public static void main(String[] args) {
int i;
if (Boolean.valueOf(args[0]).booleanValue())
i = 0;
System.out.println(i);
}
}

Attempting to compile this program prints

Test.java:6: variable i might not have been initialized


System.out.println(i);
^
1 error

The compiler performs a necessarily limited analysis in making the determination


that a local variable has been definitely assigned.117 Therefore, the error mes-
sage generated by a Java compiler states that the variable “… might not
have been initialized.” This tentative language allows for the possibility
that the “conservative” analysis performed by the compiler may be at odds with
what a programmer knows to be true. Here is another example involving a try
statement:

182 JAVA RULES


class Test {
public static void main(String[] args) {
int i;
try { i = 0; }
catch(ArithmeticException e) { }
System.out.println(i);
}
}

Attempting to compile this program generates exactly the same error message.
I like to use the try statement to emphasize that the compiler decides what is
“definite.” Assignments to local variables in a try block are never definite. That
means in order to initialize a local variable in a try block, a standard default
value must be assigned in the variable initializer. For example,

public static void redirect(String fileName) {


File out = new File(fileName);
PrintStream ps = null;
try {ps = new PrintStream(
new BufferedOutputStream(new FileOutputStream(out),128),
true);
} catch (IOException e) {
System.err.println("IOException opening redirect file " +
fileName);
}
System.setOut(ps);
}

The initialization of local variables with standard default values immedi-


ately before they are used in a try block is very common.
Blank finals that are both local variables and used in a block class must be
definitely assigned before the body of the local or anonymous class, which by

117. The authors of the JLS are frequently flogged for not having written a more detailed specifica-
tions for definite assignment and unreachable statements. I find much of the criticism to be out of
balance. For example, should the JLS include a specification that says: “If the type of the expression
in a switch statement is byte and there is a case label for all 256 integer values, the state-
ments in a default label are unreachable”? No! The specification is clear that the value of the
expressions in a case label is not taken into consideration. Dr. Gosling once said that Guy Steele
and Gilad Bracha were the only ones qualified to write the JLS because they were “well-known totally
anal freaks.” Well I’m sure Dr. Gosling meant that in a nice way, but some of the suggestions for
improving the specifications for definite assignment and unreachable statements really are “totally
anal.”

FIELDS AND METHODS 183


extension means they must be definitely assigned before the local or anonymous
class can be instantiated. There is an example of this in 1.5.4 Blank Finals.

1.8.2 Unreachable Statements


Unreachable statements generate a compiler error because they cannot com-
plete normally (read dead code). As stated in the JLS:
The idea is that there must be some possible execution path from the
beginning of the constructor, method, instance initializer or static initial-
izer that contains the statement to the statement itself.118

The simplest examples of unreachable statements involve the unconditional exe-


cutions of a control-transfer statement. For example,

while (true) {
continue;
System.out.println("unreachable statement");
}

for (int i = 0 ; i < 10 ; i++ ) {


throw new Exception();
System.out.println("unreachable statement");
}

switch (i) {
case 1:
System.out.println("reachable");
break;
System.out.println("unreachable statement");
default:
System.out.println("reachable");
}

void test() {
return;
System.out.println("unreachable statement");
}

All of these examples generate a compile error such as the following.

118. Gosling et al., §14.20, “Unreachable Statements.”

184 JAVA RULES


test.java:10: unreachable statement
System.out.println("unreachable statement");
^
1 error

Perhaps the second most obvious group of unreachable statements are loops in
which the expression is an inlined constant that evaluates to false. For example,

class Test {
static final boolean DEBUG = false;
public static void main(String[] args) {
for (;DEBUG;) {
System.out.println("unreachable statement");
}
while (DEBUG) {
System.out.println("unreachable statement");
}
do {
System.out.println("reachable"); //always executes once
}
while (DEBUG);
}
}

What this means to application programmers is that debug flags that are
inlined constants cannot be used to control the execution of a loop.
The rules for unreachable statements have been especially written to allow
the if statement to be used for conditional compilation; statements contained
in an if (false) statement are always considered reachable.
Except for loop control variables that are inlined constants, the value of an
expression is never taken into consideration when determining if a statement is
reachable. The JLS has always included a curious little mistake in this regard:
Except for the special treatment of while, do, and for statements
whose condition expression has the constant value true, the values of
expressions are not taken into account in the flow analysis.119

(Recall that “constant value” is the interim term for inlined constant in the Sec-
ond Edition.) The “whose condition expression has a constant value true”

119. Gosling et al., §14.20, “Unreachable Statements.”

FIELDS AND METHODS 185


should be replaced by “whose condition expression has a constant value.” Both
true and false matter. If the value is true, the while, do, and for loop
cannot complete normally (because it is an infinite loop), but false is also
“taken into account” as can be seen in the following specifications for the con-
tained statements in while and for loops.
The contained statement is reachable iff the while statement is
reachable and the condition expression is not a constant expression
whose value is false.119

The contained statement is reachable iff the for statement is reach-


able and the condition expression is not a constant expression whose
value is false.119

Here are a couple examples of what the specification means by not “taking into
account” the value of an expression:

for (int i=0; i<0; i++)


System.out.println("technically reachable");

switch (0) {
case 1:
System.out.println("technically reachable");
default:
System.out.println("reachable");
}

Both of these statements compile even though it is obvious they include state-
ments that are not really reachable. Here is another example made rather
famous by the Jikes compiler team at IBM:

void method(boolean b) {
if (b) return;
else return;
b = !b;
}

The last statement is bold is unreachable, but not because the value of b was
taken into consideration. It is unreachable because all of the branches in the if-
then-else statement resulted in abrupt completion.
A catch clause can also be unreachable. For example,

186 JAVA RULES


class Test {
public static void main(String[] args) {
try {
throw new RuntimeException("does not compile");
}
catch (Exception e) { }
catch (RuntimeException e) { }
}
}

Attempting to compile this program generates the following compiler error:

Test.java:7: exception java.lang.RuntimeException has already


been caught
catch (RuntimeException e) { }
^
1 error

Statements in the second catch block are unreachable because Runtime-


Exception is an Exception subclass. This is an unreachable statement
compiler error in disguise. In fact, the javac error message used to actually
say catch not reached. The discussion of unreachable catch clauses is
continued in 6.8.1 The catch Clause.

NOTE 1.4
The following section is necessary because I use the term qualifying
type to describe the class or interface that a compiler searches for the
compile-time declaration of a method (most notably in Table 1.7 The
Five General Forms and Qualifying Types). Anyone already familiar with
this terms knows that the Second Edition of the JLS introduced it pre-
cisely so as to differentiate the name of the class or interface written to
a class file from the class or interface that a compiler searches for the
compile-time declaration of a method. The two are only different, how-
ever, when the method invoked is declared in the Object class. Never-
theless, the reader should be aware that I am using the term qualifying
type with a slightly different meaning than the JLS. My reason for blur-

FIELDS AND METHODS 187


ring this distinction is simply that the class or interface that the compiler
searches has never had a name (in either edition of the JLS). It really
needs one, and qualifying type works quite well (especially when you
consider that field access and method invocation expressions are differ-
ent forms of qualified access as defined in the next chapter).

1.9 Qualifying Type versus Compile-Time Declaration


Before discussing the five general forms of field access or method invocation
expressions in the next section, it is necessary to explain the relationship
between the qualifying type of a method invocation and the compile-time
declaration of a method. The qualifying type is the name of the class or inter-
face that is written to a class file as part of the symbolic reference to a method.
The method in the qualifying type is used for symbolic resolution and dynamic
linking. As stated above, there is no name for the class or interface that a com-
piler searches for the compile-time declaration of a method. However, the method
in that class or interface is the one used to compile a method invocation expres-
sion. (More specifically, the method modifiers are used to perform
“three…checks”122 that are described in 15.12.3 Compile-Time Step 3: Is the
Chosen Method Appropriate? of the JLS.) There is a third method, the one actu-

Technically speaking, to refer to the class or interface that a com-


piler searches for the compile-time declaration of a method as the
qualifying type is incorrect, but only for methods declared in the
Object class.

ally invoked at runtime. These three methods may be all the same, or they could
all be different (but only if the method is declared in the Object class) as in the
following example of invoking an overridden toString() method.

class Test {
public static void main(String[] args) {
Superclass superclass = new Subclass();
superclass.toString();
}

188 JAVA RULES


}
class Superclass {
public String toString() {
return "this is the compile-time method";
}
}
class Subclass extends Superclass {
public String toString() {
return "this is the method invoked at run time";
}
}

Because this method is declared in the Object class the qualifying type written
to the class file is Object. This may surprise some readers because it is a
quirk in the Java programming language that was not formalized until the Second
Edition of the JLS. The qualifying type of a method declared in the Object
class is always Object, regardless of how the method is invoked. If the
method is not declared in the Object class, the qualifying type is always the
same as the class or interface searched by a compiler for the compile-time dec-
laration.
Why the difference? The Second Edition of the JLS offers no explanation for
the difference. The change, however, can be seen in the addition of the following
specification in 13.1 The Form of a Binary.
• Given a method invocation expression in a class or interface
C referencing a method named m declared in a (possibly dis-
tinct) class or interface D, we define the qualifying type of
the method invocation as follows:
If D is Object then the qualifying type of the expression is
Object.…120

What follows the ellipses is more or less an exact restatement of the rules in
15.12.1 Compile-Time Step 1: Determine Class or Interface to Search, so the
entire difference between the qualifying type and the class or interface searched
by a compiler is the last sentence quoted above.

120. Gosling et al., §13.1, “The Form of a Binary.”

FIELDS AND METHODS 189


The precise reason why this change had to be made is closely related to the
discussion in 3.7 Do Interfaces Extend the Object Class? Although undocu-
mented, interface types could be used to invoke the public methods in the
Object class as far back as the 1.0 release. In the Second Edition of the JLS
this language feature was finally documented in 9.2 Interface Members, which
states that public methods such as getClass() in the Object class are
“implicitly declared”121 in interface types. For example,

class Test {
public static void main(String[] args) {
Dummy dummy = new Dummy() {};
System.out.println(dummy.getClass());
}
}
interface Dummy { }

This program compiles and when executed prints class Test$1 (the name of
an anonymous class). The problem is that although the type of the primary
expression in dummy.getClass() is an interface, the invokeinterface
machine instruction cannot be used to invoke the public methods in the
Object class. The precise reason why is not of interest to application program-
mers, but is discussed in Bug Id 4398789. The solution is to invoke the method
using the invokevirtual machine instruction. If you read the JVMS for the
invokevirtual machine instruction you will see that the type name written
to the class file as part of the symbolic reference to the method invoked must be
a class type. To simplify the specification, the JLS states that the qualifying type
for such a method invocation is always Object.
I probably would not even broach the subject were it not for examples such as
the following involving the protected clone() and finalize() methods.

class Test {
public static void main(String[] args) {
Dummy dummy = new Dummy() {};
dummy.clone();
}

121. Gosling et al., §9.2, “Interface Members.”

190 JAVA RULES


}
interface Dummy { }

Attempting to compile this program generates the following compiler error:

Test.java:4: clone() has protected access in java.lang.Object


dummy.clone();
^
1 error

As explained in 3.7 Do Interfaces Extend the Object Class?, the clone() and
finalize() methods are “filtered out” when interfaces extend the Object
class. This example should therefore generate a cannot resolve symbol
error instead of an access control error. I believe this is why Bug Id 4644627,
“interfaces extend Object?” is still marked “In progress, bug.” This is very confus-
ing because the following interface compiles in the same 1.4.1_01 release (the
latest release as of this writing).

class Test {
public static void main(String[] args) {
Dummy dummy = null;
dummy.clone();
}
}
interface Dummy {
void clone(); //result type not Object
}

The error message generated by the first example (incorrectly) implies that inter-
faces inherit the protected clone() method from Object. The second
example implies that it is not inherited because the clone() method declared
in the Dummy interface has a void result type that would otherwise generate a
compiler error. (In fact, such interface method declarations do generate a com-
piler error in pre-1.4.1 releases.) This obviously needs to be fixed.
While on the subject of the difference between the qualifying type of a
method invocation and the class or interface that a compiler searches for the
compile-time declaration of a method, I want to address a serious problem with
the term compile-time declaration. This term is actually only used in one sec-
tion of the JLS. It is defined as follows.

FIELDS AND METHODS 191


If there is a most specific method declaration for a method invocation,
it is called the compile-time declaration for the method invoca-
tion.122

15.12.3 Compile-Time Step 3: Is the Chosen Method Appropriate? in the JLS


goes on to discuss “three further checks [that] must be made on the compile-
time declaration.”122 Those checks use the method modifiers and result type of
the “compile-time declaration” even though that may not be the method actually
invoked at run time. Thus the term compile-time declaration is used to distin-
quish the declaration that the compiler uses to perform the checks discussed in
15.12.3 Compile-Time Step 3: Is the Chosen Method Appropriate? from the
method actually invoked at run time. See 1.11.1 The Compiler-Enforced Method
Contract for a closely related discussion.
The problem with the term compile-time declaration is that the so-called dec-
laration may actually be an inherited method. For example,

class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
sub.print();
}
}
class Superclass {
void print() {
System.out.println("inherited by subclass");
}
}
class Subclass extends Superclass {
}

According to 15.12.1 Compile-Time Step 1: Determine Class or Interface to


Search, the compiler searches Subclass for the declaration of the print()

122. Gosling et al., §15.12.3, “Compile-Time Step 3: Is the Chosen Method Appropriate?”

192 JAVA RULES


method because the declared type of the sub variable is Subclass. The

When you hear the term compile-time declaration think “the class
or interface the compiler searched” not for a declaration, but for a
member with a given method signature.

method is actually declared in Superclass, however. So which method is the


“compile-time declaration”? That this is potentially very confusing is evidenced in
the following specification from the First Edition of the JLS.
A reference to a method or constructor must be resolved at compile
time to a symbolic reference to the class or interface in which the
denoted method or constructor is declared.123

This says that the name of the class or interface written to the class file is the
one in which the method is “declared.” This has never been the case. The name
of the class or interface written to the class file has always been the one the
compiler searched. This specification was corrected in the Second Edition by
the introduction of the term qualifying type:
A reference to a method must be resolved at compile time to a sym-
bolic reference to the qualifying type of the invocation.124

The thing to remember is that the compile-time declaration may be an inherited


member of the class or interface searched.

1.10 The Five General Forms


All field access and method invocation expressions have one of five general
forms (no matter how complex the source code appears). I use the term gen-
eral form throughout this book to introduce the syntax of a particular expres-
sion. For example, the general form of an array access expression is
primaryExpression[IndexExpression]. When I speak of the five
general forms, however, it is always in reference to field access and method

123. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
§13.1, “The Form of a Java Binary.” (Do not update.)
124. Gosling et al., §13.1, “The Form of a Binary.”

FIELDS AND METHODS 193


invocation expressions. Those two expressions have the same five general
forms.
Except as the identifier in a method declaration, any use of a method name
is by definition a method invocation expression. The same is not true of field
access expressions. “Access” always implies access from outside of a class
or interface. Therefore the simple name of a field or the this.fieldName
general form is technically not a field access expression. If method invocation
expressions were called method access expressions instead, the simple name
of a method and the this.methodName general form would likewise not be
considered a method “access” expression. In order to simplify the terminology, I
ignore this issue. Two of the five general forms of field access expressions are
fieldName (the simple name of a field) and this.fieldName.
The five general forms determine which class or interface the compiler
searches to find the declaration of a field or method. If the field or method is not
a member of that class, the javac compiler generates a cannot resolve sym-
bol error message. For example,

import java.util.*;
class Test {
public static void main(String[] args) {
GregorianCalendar today = new GregorianCalendar();
today.getDate();
}
}

Attempting to compile this program generates the following compiler error:

Test.java:5: cannot resolve symbol


symbol : method getDate ()
location: class java.util.GregorianCalendar
today.getDate();
^
1 error

This is an example of the primaryExpression.methodName general


form in which the primary expression is the simple name of a variable. The class
or interface searched is the declared type of the variable, which in this case is

194 JAVA RULES


GregorianCalendar. The problem here is that the correct method name is
getTime(), not getDate().
The cannot resolve symbol compiler error is one of the most com-
mon compiler errors in the language. This compiler error is confusing at first.
The word symbol refers to symbolic references. The compiler is using terminol-
ogy that is normally associated with class files and the JVM. As an application
programmer, you would refer to these symbolic references as simply names.
The first thing the compiler must do is to “determine the meaning”125 of a name
(to use the language of the JLS). The meaning of a name is always the fully qual-
ified name. In class files, fully qualified names are referred to as symbolic ref-
erences. The compiler generates the cannot resolve symbol error
message if the entity could not be found in the class or interface searched.
Look again at the cannot resolve symbol error message above. It has
three parts. The first line includes the source code (or .java) file name fol-
lowed by the line number and the cannot resolve symbol error message.
The second line always begins with the word symbol followed by what kind of
entity the compiler was searching for (based on the syntactic classification of
names according to context) and the name of that entity. There are four possibil-
ities:

symbol : class …
symbol : constructor …
symbol : method …
symbol : variable …

Notice that there is no symbol : field. The compiler has no idea how to
determine that an unqualified variable name is supposed to be a field. It could
just as easily be a local variable or parameter, so variable is always used in
the error message. The third part of the message is what interests us the most
in this context. The third line always begins with the word location followed
by either the class or interface keyword and then the name of the class
or interface searched. The type name in a cannot resolve symbol error
message is always fully qualified. Be on the alert for simple types names. They

125. Gosling et al., The Java Language Specification, Introduction to Chapter 6, “Names.”

FIELDS AND METHODS 195


indicate the compiler was searching the unnamed package. This is helpful in
debugging the problem of declaring a class or interface with the same simple
name as one of the types in the core API.
Whenever you see one of these cannot resolve symbol error mes-
sages, the first thing you should do is check the name of the entity to see if it is
misspelled (as in the example above). Failing that, look at the location line to
see if the class or interface searched is what you expected. If the class or inter-

Table 1.7 The Five General Forms and Qualifying Types


General Form Qualifying Type (or location)
fieldName For package members, these are the same as
methodName this.fieldName and
this.MethodName in a non-static
context or TypeName.fieldName and
TypeName.methodName in a static
context (only TypeName is implicit and is the
name of the class or interface in which the
simple name appears). For nested types, see
1.10.1 The Meaning of a Simple Field or
Method Name.

this.fieldName The compiler searches the class in which the


this.MethodName this keyword appears, unless this is
qualified. If this is qualified, the compiler
searches the qualifying class name.

super.fieldName The compiler searches the direct superclass of


super.methodName the class in which the super keyword
appears. If super is qualified, the compiler
searches the direct superclass of the qualifying
class name.

TypeName.fieldName The compiler searches TypeName.a I


TypeName.methodName sometimes refer to this as the static
member general form.

196 JAVA RULES


Table 1.7 The Five General Forms and Qualifying Types (Continued)
General Form Qualifying Type (or location)
primaryExpression.fieldName A compiler error is generated if the type of the
primaryExpression.methodName primary expression is not a reference type. This
is by far the most common general form and
definitely needs a name. Logically enough, I
refer to this as the primary expression
general form. The reference type searched
depends on the type of the primary expression.
There are four possibilities, one for each kind of
primary expression. The remaining rows in this
table describe the four different kinds of
primary expression.

REFERENCE TYPE LITERALS comprised of The compiler searches the String class or
String literals (very unusual) and class the class name in the class literal.
literals

VARIABLES comprised of local variables and The compiler searches the declared type of the
parameters, the simple name of a field, field field, local variable, or parameter. In the case of
access expressions, and array access an array access expression, the compiler
expressions.b searches the component type.

METHOD INVOCATION EXPRESSIONS The compiler searches the result type of


(unless the method is declared void)c method invoked.

ANY USE OF THE new KEYWORD comprised The compiler searches the class or interface
of class instance and array creation named in a class instance creation expression.
expressions (the latter is rare) In the case of an array creation expression, the
compiler searches the component type.
a. Before the 1.2 release, the javac compiler “silently tolerated” use of the TypeName.fieldName gen-
eral form to reference instance variables as if it had been written this.fieldName. (See Bug Id 4087127.)
b. As presented in 4.2.1 Primary Expressions, the this keyword is included in this list. It is excluded here
because this.fieldName and this.methodName are presented as separate general forms.
c. Methods declared void do not have a type, and therefore cannot be used as the primary expression in this
context.

face searched is not what you expected, you should analyze the code using the
five general forms listed in Table 1.7.

FIELDS AND METHODS 197


1.10.1 The Meaning of a Simple Field or Method Name
Some Java books say that the simple name of a field or method is implicitly qual-
ified by this (a reference to the current object). This is a very common mis-
take. Even the venerable John Rose starts out on the straight and narrow in the
following quote from the Inner Classes Specification, but then generalizes and
says something that simply is not true.
Remember that an instance variable reference m has the same mean-
ing as a field reference expression this.m, so the current instance is
implicitly used. In a given piece of code, all members of all current
classes are in scope and usable (except for name hiding by intervening
scopes). The simple name of a member is implicitly qualified by the cur-
rent instance in whose class the name was found… 126

In a non- static context, qualifying the name of a static member with this
works, but only because the type of this is used by the compiler, not the
this reference. See 1.10.4 Accessing static Members using a Primary
Expression for a detailed discussion. If you try that in a static context, how-
ever, a compiler error is generated (because this is undefined in a static
context). The statement that the simple name of a field or method is the same as
the this.fieldName or this.methodName general forms is a gross
oversimplification that only applies to instance variables and instance methods
referenced from within the body of the same package member in which they are
declared.
An entirely difference approach to this subject is required, one that equally
emphasizes the following idea.

The simple name of a field or method in a static context is implic-


itly qualified by a type name, not this.

Which type name depends on whether the simple name is used in a package
member or nested type. The rule for package members is stated as follows in
Table 1.7.

126. Rose, “How do inner classes affect the idea of this in Java code?”

198 JAVA RULES


For package members, [the simple name of a field or method] is the
same as this.fieldName and this.MethodName in a non-
static context or TypeName.fieldName and
TypeName.methodName in a static context (only TypeName is
implicit and is the name of the class or interface in which the simple
name appears).

I know of no other Java book that suggests the simple name of a field or method
in a static context is implicitly qualified by the name of the class or interface
in which the simple name appears. It is a novel idea, but one that is extremely
useful in a classroom setting. The closest the specifications come to saying
something like this is the following quote from the JVMS.
A class method may refer to other fields and methods of the class by
simple name only if they are class methods and class (static) vari-
ables.127

The JLS says even less, —that the simple name of an instance variable in a
static context generates a compiler error.128
Determining the meaning of a simple field or method name used in a nested
type requires searching all of the enclosing types as well as any blocks that may
enclose a local or anonymous class. Because of scoping rules, the innermost
enclosing type must be searched first, followed by any blocks that may enclose
a local or anonymous class, and then the remaining enclosing classes. For
example,

class Test {
public static void main(String[] args) {
A.B.C abc = new A().new B().new C(); //don't try this at home
abc.test();
}
}
class A {
String s = "outermost enclosing class";
class B {
String s = "enclosing class";

127. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, Second Edition,
(Boston: Addison-Wesley, 1999), §2.10.3, “Method Modifiers.”
128. Gosling et al., §6.5.6, “Meaning of Expression Names.”

FIELDS AND METHODS 199


class C {
String s = "innermost enclosing class";
void test() {
String s = "enclosing block";
class LocalClass {
LocalClass() { System.out.println(s); }
}
new LocalClass();
}
}
}
}

Executing this program initially prints enclosing block. If the declaration of


s is removed from the test() method, innermost enclosing class
would print. If the declaration of s were then removed from class C, the same
program would print enclosing class. If the declaration of s were then
removed from class B, the same program would print outermost
enclosing class. Finally. if the declaration of s were then removed from
class A, a cannot resolve symbol error message is generated. The
results would be exactly the same were if nested top-level classes were used
instead of inner member classes, only the class instance creation expression
would have to be changed to new A.B.C() because top-level classes do not
have enclosing instances.
As this example shows, the “meaning” of a simple field name may be a local
variable or parameter (that would necessarily be declared final). That is why
John Rose is careful to use the “innermost lexically apparent definition” in the fol-
lowing passage from the original Inner Classes Specification:
Some of the detailed descriptions of name binding in the 1.0 Java Lan-
guage Specification require amendment to reflect the new regularity in
lexical scoping. For example, a simple variable name refers to the
innermost lexically apparent definition, whether that definition comes
from a class or a block.129

129. Rose, “How does the Java Language Specification change for inner classes?”

200 JAVA RULES


8.1.2 Inner Classes and Enclosing Instances in the JLS does not even mention
blocks in relation to determining the meaning of a simple field name. It is not
clear from the section name that it should, but it does include the following:
When an inner class refers to an instance variable that is a member of
a lexically enclosing class, the variable of the corresponding lexically
enclosing instance is used.130

I think this is an error of omission that should be fixed in the next edition.
In the previous example, the variable s has four different meanings:
The local variable s
this.s
C.this.s
B.this.s
A.this.s
This further complicates the notion that the simple name of a field or method is
implicitly qualified by either this (non-static members) or the name of the
type in which the simple name appears (static members). For example:

class Test {
private int x = 0;
class InnerClass {
void test() {
System.out.println(x);
}
}
}

This compiles because inner classes can access the private members of
enclosing classes. Now qualify the simple name x with the this keyword:

class Test {
private int x = 0;
class InnerClass {

130. Gosling et al., §8.1.2, “Inner Classes and Enclosing Instances.” Interestingly, subsection
6.5.6.1 Simple Expression Names of 6.5.6 Meaning of Expression Names did not have to be
changed to specify that the meaning of a simple field name in a block class may be a local variable
or parameter.

FIELDS AND METHODS 201


void test() {
System.out.println(this.x);
}
}
}

Attempting to compile this class generates the following error message:

Test.java:5: cannot resolve symbol


symbol : variable x
location: class Test.InnerClass
System.out.println(this.x);
^
1 error

This is an example of this.fieldName general form, which is explained as


follows in Table 1.7.
The compiler searches the class in which the this keyword appears,
unless this is qualified. If this is qualified, the compiler searches
the qualifying class name.

A qualified this keyword should have been used. The following code does com-
pile.

class Test {
private int x = 0;
class InnerClass {
void test() {
System.out.println(Test.this.x);
}
}
}

See 3.6.2 Qualifying the this Keyword in Volume 1 for a detailed discussion of
multiple current instances.
There is a similar problem when saying that the simple name of a field or
method in a static context is implicitly qualified by a type name. In a contain-
ment or inner class hierarchy,131 there is more than one enclosing type. For exam-
ple,

202 JAVA RULES


class Test {
private static int x = 0;
static class NestedTopLevelClass {
static void test() {
System.out.println(x);
}
}
}

This is comparable to the last example, only the test() method is now a
static context. Now qualify the simple name x with the name of the
NestedTopLevel class:

class Test {
private static int x = 0;
static class NestedTopLevelClass {
static void test() {
System.out.println(NestedTopLevelClass.x);
}
}
}

Attempting to compile the Test class generates the following compiler error:

Test.java:5: cannot resolve symbol


symbol : variable x
location: class Test.NestedTopLevelClass
System.out.println(NestedTopLevelClass.x);
^
1 error

In this case, Test.x should have been used.


The meaning of a simple type name (as opposed to a simple field or method
name) is discussed in 2.2.2 Disambiguating Type Names. Nested types cannot

131. Basically, the term containment hierarchy refers to a package member and all of the
nested top-level classes declared in that package member. An inner class hierarchy refers
to all of the inner classes (inner member, local, and anonymous classes) in a given top-level
class. Contrary to the JLS, I define top-level class as any class declared static (the same as
John Rose did in the Inner Classes Specification). That means the class at the top of an inner
class hierarchy may be either a package member or a nested top-level class. See 2.12 Containment
and Inner Class Hierarchies in Volume 1 for a complete definition of these terms. Be advised that I
repeat this footnote a number of different times in Volume 2 because these terms are so unusual.

FIELDS AND METHODS 203


have the same name as any of their enclosing types, so the meaning of a simple
type name is not complicated by containment and inner class hierarchies.

1.10.2 Method Invocation Chaining


Much like the discussion of coding local variable declaration statements in a loop
in 1.7 Local Variables, this section is all about intermediate results. The creative
use of primary expressions in the primaryExpression.methodName
general form can produce some highly efficient and compact code in which inter-
mediate results are immediately used rather than being stored in a local vari-
able. More precisely, when the primary expression in a method invocation
expression is another method invocation expression (or a class instance cre-
ation expression) the primaryExpression.methodName general form is
referred to as method invocation chaining. For example,

String className = obj.getClass().getName();

A programmer that uses method invocation chaining is only interested in the


result of the last method invoked, such as getName() in the previous exam-
ple. There is no reason to store the intermediate result from the getClass()

Any method that would otherwise be declared void is a candidate


for returning a reference to the current object (return this;) to
facilitate method invocation chaining.

method invocation. The efficient thing to do in that case is to chain the method
invocations together. This works because the result type of getClass() is
Class , and getName() is a member of the Class class. Here is an example
involving a class instance creation expression:

boolean b = new Boolean(args[0]).booleanValue();

There are two intermediate results in this example. One is the args[0] array
access expression, which is used to create a Boolean object. The value of
that class instance creation expression is then immediately used as the target
reference in a method invocation expression. The Boolean object is created,

204 JAVA RULES


immediately used, and becomes eligible for garbage collection all in one state-
ment. This example is interesting because the only reason the Boolean object
is created is to get to the parsing code in the booleanValue() method.

You should never have to create an object just to get at the code in
an instance method.

Any time you see code like this there is a need for a class method that does the
same thing. Thus Bug Ids 4302078 and 4262398 (both filed in 1999) which
eventually resulted in the addition of the Boolean.valueOf(boolean b)
factory method in the 1.4 release (years later).
I find it very interesting that invocation chaining was emphasized in the
design of the java.nio package. The document entitled NIO APIs: Beta 3
Changes in the 1.4 release included the following last minute change:
Improved invocation chaining: Revised the methods Datagram-
Channel.connect, DatagramChannel.disconnect,
FileChannel.position(long), FileChannel.truncate
(long), SelectableChannel.configureBlocking,
SelectionKey.interestOps(int), Selector.mwakeup,
Matcher.appendReplacement, Matcher.reset(), and
Matcher.reset(java.lang.CharSequence) to return the object
upon which they are invoked. Revised Matcher.appendTail to return
the string buffer object passed to it.132

The java.util.logging package is very well designed, but it could have


benefited from invocation chaining instead of using so many void methods. It
really get tedious programming method invocation expression after method invo-
cation expression when the target object is always the same.

132. “NIO APIs: Beta 3 Changes” document in the API docs for in the 1.4 release.

FIELDS AND METHODS 205


1.10.3 Casting a Target Reference
The target reference in a field access or method invocation expression can be
cast to a superclass type in order to access a hidden field or invoke a hidden
class method. For example,

class Test {
public static void main(String[] args) {
Subclass subclass = new Subclass();
System.out.println((Superclass) subclass.s);
(Superclass) subclass.print();
}
}
class Superclass {
public String s = "hidden field";
public static void print() {
System.out.println("hidden class method");
}
}
class Subclass extends Superclass {
public String s = "subclass field";
public static void print() {
System.out.println("subclass method");
}
}

In this example, subclass is the target reference. The programmer is


attempting to cast subclass to the Superclass type. Rather than casting
the target reference, however, the cast operators are casting the value of the
field access expression and the return value of the method invocation expres-
sion. Attempting to compile this program generates the following compiler
errors:

Test.java:5: not a statement


(Superclass) subclass.print();
^
Test.java:4: inconvertible types
found : java.lang.String
required: Superclass
System.out.println((Superclass) subclass.s);
^
2 errors

206 JAVA RULES


The problem is that the cast operator is a unary operator that has at most one
operand. Thus when casting a target reference, parentheses are required to
override this normal order of evaluation. For example,

class Test {
public static void main(String[] args) {
Subclass subclass = new Subclass();
System.out.println(((Superclass)subclass).s);
((Superclass)subclass).print();
}
}

This is the same program as before, only using parentheses to enclose both the
cast operator and the target reference. This program does compile, and when
executed prints

hidden field
hidden class method

Note that both the cast operator and the target reference must be enclosed in
parentheses. Furthermore, the closing parentheses comes before the period
separator. “Breaking apart” a field access or method invocation expression like
this can take a little getting used to, and explains in part why casting target refer-
ences is discussed in this chapter (apart from the other uses of a cast operator
in 5.7.4 The Cast Operator).
Casting a target reference has the effect of telling the compiler to search in
a different class or interface. It is therefore conceptually different from other
type conversions. For example,

import java.util.Stack;
class Test {
public static void main(String[] args) {
Stack stack = new Stack();
stack.push(new String("abc"));
System.out.println(((String)stack.pop()).toUpperCase());
}
}

Executing this program prints ABC. The primary expression is stack.pop(),


the result type of which is Object (an untyped reference).133 The problem is

FIELDS AND METHODS 207


that there is no toUpperCase() method in the Object class. The cast oper-
ator in effect “tells the compiler” where to find the toUpperCase method.

1.10.4 Accessing static Members using a Primary Expression


The NullPointerException is thrown when using the primary expression
general form to access an instance variable, instance method, or inner member
class.134 Such entities cannot exist apart from an instance of the class in which
they are declared, which means trying to access them using a null reference
is meaningless. The same is not true, however, for static members. As dis-
cussed in 3.2 The static Modifier in Volume 1, class variables, class meth-
ods, and nested top-level classes and interfaces exist apart from any instances
of the class in which they are declared. Such members are usually accessed
using the static member general form, but they can be accessed using the
primary expression general form as well. For example,

class Test {
static String s = "this is a class variable";
public static void main(String[] args) {
Test test = null;
System.out.println(test.s);
test.print();
}
static void print() {
System.out.println("this is a class method");
}
}

Executing this program prints

this is a class variable


this is a class method

133. Technically speaking, the primary expression is ((String)stack.pop()) because


parenthesized expressions are regarded as primary expressions in the JLS. See 4.3.2 Operator
Order of Precedence and Parenthesized Expressions for a discussion.;
134. The name of this exception is actually rather curious. I have never read anything about the his-
tory of it, but I am sure it must date back to the very inception of the language. One might expect it
to have been called the NullReferenceException, especially because very early implemen-
tations of the JVM did not use pointers to reference fields.

208 JAVA RULES


If test is initialized with null, why isn’t a NullPointerException is
thrown? The answer is that the JVM never sees what the programmer wrote.
Once the compiler determines that a class variable field is being accessed or a
class method is being invoked, only the type of the primary expression is
used. In this example, the type of the primary expression test is Test. What
the compiler writes to the class file is Test.s and Test.print(), which
explains why NullPointerException is not thrown when accessing
static members. Having explained this, I should point out that using a primary
expression to reference static members is discouraged in Code Conven-
tions for the Java Programming Language.135

1.11 Method Signatures


Method signatures are how methods and constructors are differentiated at com-
pile-time. They are important because the definition of hiding, overloading, and
overriding is based on method signatures: subclass methods are said to hide or
override superclass methods that have the same signature; overloaded methods
have the same name, but they are different methods because their signatures
are not the same.
Overloading and overriding are easily confused. They are somewhat close in
meaning because the definitions of both overloading and overriding include
methods that have the same name. The similarity stops there, however. The dif-
ferences between overloading and overriding are summarized in Table 1.8. The
most significant of these differences is that overloaded methods are not bound
by the compiler-enforced method contract. As stated in the JLS:
There is no required relationship between the return types or between
the throws clauses of two methods with the same name but different
signatures.136

Overloading and overriding are discussed further in the following subsections.


Hiding is not discussed until the beginning of the next chapter.

135. Unascribed, §7.9, “try-catch Statements.”


136. Gosling et al., §8.4.7, “Overloading.”

FIELDS AND METHODS 209


Method signatures are best thought of in terms of the method descriptors
written to a class file and subsequently used during dynamic method lookup.
Method descriptors encode the result type as well as the parameter types and
method name. The following example is a symbolic reference for the
println() method used in the ubiquitous “Hello World!” program (as would be
found in the constant pool of a class file).

java.io.PrintStream.println(Ljava/lang/String;)V

The V at the end of this symbolic reference is the result type, which is void. At
the front of the symbolic reference is the fully qualified class or interface name in
which the method is declared. In the middle is the method signature, which in

Table 1.8 Overloading versus Overriding


Overloading Overriding

Overloaded methods are typically Overriding methods are necessarily declared in


declared in the same class or interface. different (but related) classes within the same
class hierarchy.

Overloaded methods have different Overriding methods have the same method
method signatures. They are not bound signatures. They are therefore bound by the
by the compiler-enforced method compiler-enforced method contract.
contract.

Arguably, overloaded methods are the Client programmers expect overriding methods
same method in the mind of a client to implement a different, more specialized
programmer. behavior.

Overloaded method matching is Overriding methods use dynamic method lookup


resolved at compile time. See 5.8 at run time.
Overloaded Method Matching for a
discussion.

Though sometimes confused with Overriding is an important part of polymorphism.


operator overloading (which is thought While a given class of objects may appear to
of as ad hoc polymorphism), method have “many forms,” they must always behave
overloading has nothing to do with the same.
polymorphism. See 5.4 Substitution is a
Higher Concept than Polymorphism for
a detailed discussion.

210 JAVA RULES


this case is println(Ljava/lang/String;). That is the method name
following by the descriptor encoding of the parameter type String. (The
descriptor encoding of reference types always begins with the capital letter L
followed by the fully qualified class or interface type using forward slashes as
separators and then a semicolon.) This example of a symbolic reference should
help you better understand why the term signature is used to describe the
method name and parameter list. As with println(Ljava/lang/
String;), each method signature is unique within a given class or interface.
Parameter names are not part of a method signature. Believe it of not, local
variable and parameter names are not even written to class files. For example,

class Test {
public static void main(String[] args) {
String local = args[0];
}
}

The decompiled code for the Test class is as follows:

Compiled from Test.java


class Test extends java.lang.Object {
Test();
public static void main(java.lang.String[]);
}

Method Test()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 return

Method void main(java.lang.String[])


0 aload_0
1 iconst_0
2 aaload
3 astore_1
4 return

Look closely and you will see that args and local appear nowhere in this out-
put. There is a detailed discussion of this in 1.7 Local Variables.
There are four components to a method signature.

FIELDS AND METHODS 211


• The method name
• The number of parameters
• The type of each parameter
• The order of the parameters
Change any one of these and what you have is a different method. The order
of parameters is often overlooked in discussions about method signa-
tures. This may be the fault of the JLS. The applicable specification is somewhat
buried in a discussion of invocation modes. Here is the entire 8.4.2 Method Sig-
nature from the JLS:
The signature of a method consists of the name of the method and
the number and types of formal parameters to the method. A class
may not declare two methods with the same signature, or a compile-
time error occurs.

The example:

class Point implements Move {


int x, y;
abstract void move(int dx, int dy);
void move(int dx, int dy) { x += dx; y += dy; }
}

causes a compile-time error because it declares two move methods


with the same signature. This is an error even though one of the decla-
rations is abstract.137

There is no mention of order anywhere. Yet another section includes the follow-
ing specification.
The following compile-time information is then associated with the
method invocation for use at run time:

• The number of parameters and the types of the parameters,


in order.138 [emphases added]

137. Gosling et al., §8.4.2, “Method Signature.”


138. Gosling et al., §15.12.3 Compile-Time Step 3: Is the Chosen Method Appropriate?

212 JAVA RULES


In fact, programmers have been known to change the order of constructor
parameters in order to have two constructors with the same number and type of
parameters. Doing so is a mistake, not a matter of style. Method or construc-
tor signatures that vary only in the order of their parameters are too easily con-
fused. Factory methods should be used instead. See 1.3.3.2 Alternative
Constructor Designs for a discussion.
Note that the above list does not include the final modifier. Declaring a
method or constructor parameter final does not change the method signa-
ture. That means subclasses can declare the parameters of a hiding or overrid-
ing method final even if those parameters are not declared final in the
superclass method, and vice versa.
The result type is also not part of the method signature. For example,

class Test {
public static void main(String[] args) {
String s = test("echo");
}
static String test(Object o) {
System.out.println("test(Object o)");
}
static void test(String s) {
System.out.println("test(String s)");
}
}

The matching method is test(String s), which is a void method. Hence


attempting to compile this program generates the following compiler error:

Test.java:3: incompatible types


found : void
required: java.lang.String
String s = test("echo");
^
1 error

Why is the result type not part of the method signature in the Java programming
language? Why exclude the result type, but include the parameter types? These
are reasonable questions because of examples such as the previous one.

FIELDS AND METHODS 213


The problem with including the result type in a method signature is top-level
expressions that invoke overloaded methods. Consider for example a method
invocation expression on a separate line of code (an expression statement). If
the method returns a value, that value is said to be quietly discarded. Here is
an example of such an expression statement:

doSomething();

Now suppose doSomething() is overloaded. How can the compiler deter-


mine which of the overloaded doSomething() methods to invoke? It cannot.
Hence, method signatures do not include the result type. This does not mean,
however, that you can change the result type in a method declaration. Doing so
may result in a NoSuchMethodError being thrown at run time because
changing the result type of a method breaks compatible with existing binaries.
The remainder of this section explains why.
At the same time dynamic linking checks that a method still exists it also
checks for a change in the result type. A method exists if it can be found in the
appropriate method dispatch table. This effectively checks for a change in the
result type because the method descriptor (not the method signature) is used
to search the method dispatch table. As shown above, the method descriptor is
the result type plus the method signature. If the result type is not the same, the
method is not found. For example,

class IncompatibleChange {
private int count = 0;
public int getCount() {
return count;
}
}

As would be expected, executing the following program prints 0.

class Test {
public static void main(String[] args) {
IncompatibleChange incompatibleChange =
new IncompatibleChange();
System.out.println(incompatibleChange.getCount());
}
}

214 JAVA RULES


If, however, IncompatibleChange is recompiled using long instead of
int as the result type of getCount(), executing the same program throws
the following exception:

Exception in thread "main" java.lang.NoSuchMethodError:


IncompatibleChange.getCount()I
at Test.main(Test.java:4)

One effect of this implicit check of the result type is that a void method cannot
be substituted for a method that has a different result type. This is important
because the compiler checks to make sure that void methods are only invoked
in top-level expressions. As discussed in one of the following subsections, how-
ever, a future release of the Java programming language may allow for Covari-
ant result types in which subclasses can override superclass methods using a
narrower result type that is assignment compatible with the result type of the
overridden method.

1.11.1 The Compiler-Enforced Method Contract


This section does not seek to change the established meaning of contract as
defined in the JLS:
Binaries are compiled to rely on the accessible members and construc-
tors of other classes and interfaces. To preserve binary compatibility, a
class or interface should treat its accessible members and construc-
tors, their existence and behavior, as a contract with its users.139

Quite the contrary, this sections explains the extent to which the compiler
enforces this contract. The term method contract is also used to refer to the
API docs for a particular method. When so used, method contract is more or
less synonymous with interface contract. To avoid any ambiguity in meaning, I
consistently use the term compiler-enforced method contract when refer-
ring to the rules discussed in this section. The compiler-enforced method con-
tract is not about enforcing the interface contract (or API docs) for a particular
method (referred to as “behavior” in the above quote) because it only applies to

139. Gosling et al., §13.2, “What Binary Compatibility Is and Is Not.”

FIELDS AND METHODS 215


the access modifier, result type, and throws clause of hiding and overriding
methods. Nevertheless, these rules are closely related to the normal use of the
term contract, in particular “the existence” of “accessible” methods as referred
to in the above quote from the JLS. The JLS even makes reference to “the con-
tract between the implementor and user of the method” when specifying one of
the rules discussed in this section:
The checked exception classes named in the throws clause are part
of the contract between the implementor and user of the method or
constructor. The throws clause of an overriding method may not
specify that this method will result in throwing any checked exception
which the overridden method is not permitted, by its throws clause,
to throw. 140

It is this passage from the JLS in particular that led me to name these rules the
compiler-enforced method contract. Nevertheless, I should point out that this
term is of my own making. It is not used in 8.4.6.3 Requirements in Overloading
and Hiding, which is the corresponding section in the JLS.
The compiler-enforced method contract always involves subclass methods
that have the same signature as a superclass or superinterface method. There
are exactly three cases in which the compiler enforces the method contract.
• Hiding methods: I believe this is an unnecessary language restriction that
could be lifted (though I seriously doubt it ever will be). There is a discussion
of this below.
• Overriding methods: This includes implementing abstract methods
inherited from either a superclass or superinterface. It also includes inherit-
ing a non-abstract method from a superclass that has the same signa-
ture as one or more abstract methods inherited from superinterfaces. In
that case the superclass method overrides and implements the superinter-
face method(s).
• More Than One abstract Method With The Same Signature: This is a
special case that can only happen when at least one of the abstract
methods is inherited from an interface. The abstract methods are bound
by the method contract with the notable exception of the throws clause.

140. Gosling et al., §11.2, “Compile-Time Checking of Exceptions.”

216 JAVA RULES


This exception to the rule for throws clauses is discussed in the following
subsection.
Overloaded methods have the same name, but not the same signature. They
are therefore not bound by the compiler-enforced method contract. In
1.11.2 Overloaded Methods, I argue that overloaded methods are “the same
method in the mind of a client programmer.” Nevertheless, overloaded methods
may in fact have different access modifiers, different result types, and throw a
completely different set of checked exceptions. That does not mean that they
should, but it is technically possible in that no compiler errors are generated.
The access modifier, result type, and throws clause are part of the com-
piler-enforced method contract. The rules are summarized as follows:
• The hiding, overriding, or abstract method must have the same result
type as the hidden, overridden, or other abstract methods. One cannot
have a result type and the other be declared void.
• The hiding, overriding, or abstract method must be as accessible as the
hidden, overridden, or other abstract methods. This means any method
that implements an interface method must be declared public because
interface methods are implicitly public. In the case of more than one
abstract method with the same signature, this means all of them must
be public because at least one is inherited from an interface.
• Hiding or overriding methods cannot throw a checked exception that is not
assignment compatible with an exception thrown by as the hidden or over-
ridden method. By extension, if the compile-time declaration does not throw
any checked exceptions, neither can the hiding or overriding method.
• A class method can only be hidden by another class method. Likewise, only
an instance method can override another instance method. Class methods
are fundamentally different from instance methods. Instance methods pass
a reference to the target object; static methods do not. An instance
method cannot be substituted for a static method because this and
super are undefined in a static context.
These compiler checks are critically important to an interpreted language such
as Java. They allow a JVM to assume that an overriding method can be substi-
tuted without having to check the method modifiers. That assumption is criti-
cally important to the overall design of dynamic linking because the

FIELDS AND METHODS 217


symbolic reference to a method is only resolved once, the first time the
method is invoked. If after having been resolved, a different method is substi-
tuted as the result of dynamic method lookup, the runtime system does not
check the method modifiers again. (The throws throws is only checked at
compile time.)
I going to step out on a limb here and suggest that these compiler checks
are an unnecessary language restriction for hidden methods. Only static
methods are hidden, and static methods are not substituted for one another
at run time. In other words, they do not use dynamic method lookup. I suspect
that these compiler checks are in place for hidden methods merely as a way to
discourage hiding.
The access modifiers, static, and abstract are the only method modi-
fiers involved in the compiler-enforced method contract. The rule for accessibil-
ity of subclass methods with the same signature dictates the relationships in
Table 1.9. The other method modifiers are final, synchronized,

Table 1.9 Accessibility of Hiding or Overriding Methodsa


Superclass Method Subclass Method

public public
protected public or protected
none (default) public, protected, or none (default)
a. Note that private methods cannot be hidden. Subclasses are therefore not bound by the compiler-en-
forced method contract when declaring a method with the same signature as a private superclass method.

native , and strictfp. Any use of the final modifier precludes this entire
discussion because methods cannot be abstract final, and of course
final methods cannot be hidden or overridden. There are no restrictions on
the use synchronized, native, and strictfp when hiding or overrid-
ing. For example, methods that hide or override synchronized methods do
not have to be declared synchronized.
There are a couple very interesting exceptions to the compiler-enforced
method contract. The first concerns access control. As stated in the JLS:

218 JAVA RULES


Perhaps surprisingly, the binary format is defined so that changing a
member or constructor to be more accessible does not cause a link-
age error when a subclass (already) defines a method to have less
access.141

Though not immediately obvious, there is a closely related comment in regards


to the binary compatibility of throws clauses:
Changes to the throws clause of methods or constructors do not
break compatibility with existing binaries; these clauses are checked
only at compile time.142

What we are talking about here are changes to the superclass method that in
effect invalidate the assumption that overriding methods can be safely substi-
tuted at run time. This is a little known quirk in dynamic linking that generally
goes unnoticed. For example,

package com.javarules.examples;
import java.io.*;
public class Superclass {
void print() throws IOException {
System.out.println("superclass");
}
}

The following declaration of Subclass (in a different compilation unit) extends


Superclass.

package com.javarules.examples;
import java.io.*;
public class Subclass extends Superclass {
void print() throws IOException {
System.out.println("subclass");
}
}

Both classes are members of the unnamed package and have package-private
print() methods that throws IOException. Now suppose the following
changes are made to Superclass without recompiling the Subclass.

141. Gosling et al., §13.4.6, “Access to Members and Constructors.”


142. Gosling et al., §13.4.19, “Method and Constructor Throws.”

FIELDS AND METHODS 219


package com.javarules.examples;
import java.io.*;
public class Superclass {
public void print() throws FileNotFoundException {
System.out.println("superclass");
}
}

The print() method is now more accessible in Superclass and throws


FileNotFoundException instead of the more general IOException.
Look what happens in the following program.

import com.javarules.examples.*;
import java.io.*;
class Test {
public static void main(String[] args) {
Superclass sub = new Subclass();
try {
sub.print();
} catch(FileNotFoundException e) { }
}
}

Executing this program prints subclass. This program has just invoked a
package-private method in a different package inside of a try statement that
cannot catch the checked exception thrown by that method.
One need only ponder the design of the compiler-enforced method contract
and dynamic linking for a moment to realize there is really nothing that can be
done about this. The superclass programmer opens up a hole in the access con-
trol mechanism by declaring an overridden method to be more public than
the corresponding subclass method. Likewise, adding a checked exception to a
superclass throws clause that is narrower than any thrown in the correspond-
ing subclass methods invalidates the subclass exception specification. These
problem will be caught when the subclasses are recompiled. For example,
recompiling Subclass as shown above generates the following compiler error.

com/javarules/examples/Subclass.java:4: print() in
com.javarules.examples.Subclass cannot override print() in
com.javarules.examples.Superclass; attempting to assign weaker

220 JAVA RULES


access
privileges; was public
void print() throws IOException {
^
1 error

If this problem is fixed by declaring the print() method in the subclass


public, then the following error is generated on the next attempt to recompile.

com/javarules/examples/Subclass.java:4: print() in
com.javarules.examples.Subclass cannot override print() in
com.javarules.examples.Superclass; overridden method does not
throw java.io.IOException
public void print() throws IOException {
^
1 error

So both problems must be fixed before Subclass can be recompiled. The


other exception to the compiler-enforced method contract is discussed in the
next section.

1.11.1.1 throws Clause Conflicts in abstract Methods


This section discusses an important exception to the compiler-enforced method
contract. The exception applies when there are two abstract methods with
the same method signature in what would necessarily be an abstract class.
These are always regarded as different methods. In other words, one does not
override the other. There are two cases to consider:
• An abstract method declared somewhere in the class hierarchy has the
same method signature as a method inherited from an interface
• More than one abstract method with the same signature is inherited
from superinterfaces. See 3.8.2 Ambiguous Names Related to Inheritance
for a discussion
In either case, the abstract methods are bound by most of the compiler-
enforced method contract. For example,

interface Superinterface {
double test();
}

FIELDS AND METHODS 221


abstract class Superclass {
public abstract int test();
}
abstract class Test extends Superclass
implements Superinterface { }

Attempting to compile this class generates the following compiler error:

Test.java:7: test() in Superclass cannot implement test() in


Superinterface; attempting to use incompatible return type
found : int
required: double
abstract class Test extends Superclass
^
1 error

And if the public modifier is removed from the test() method in Super-
class , the same code generates the following compiler error:

Test.java:7: test() in Superclass cannot implement test() in


Superinterface; attempting to assign weaker access privileges;
was public
abstract class Test extends Superclass
^
1 error

The same abstract methods, however, can throw entirely different checked
exceptions. For example,

class Test extends Superclass implements Superinterface {


public void test() { }
}
interface Superinterface {
void test() throws A;
}
abstract class Superclass {
public abstract void test() throws B;
}
class A extends Exception {}
class B extends Exception {}

This is an example of a throws clause conflict that is resolved by the imple-


menting method. Normally, hiding or overriding methods cannot throw checked

222 JAVA RULES


exceptions that are not assignment compatible with an exception thrown by the
corresponding superclass or superinterface method. In this example, however,
the abstract test() methods are considered different methods. The
checked exceptions thrown are not even related. This throws clause conflict
can only be resolved by a Subclass implementation that does not throw any
checked exceptions.
The rationale for this exception to the compiler-enforced method contract is
that conflicts between the throws clauses in different abstract methods
with the same signature may be resolved by an implementation that either
throws fewer checked exceptions or that does not throw any checked excep-
tions (as in the example above). “Fewer checked exceptions” means that the
implementation can only throw checked exceptions that are assignment compat-
ible with all of the checked exceptions thrown by the other abstract methods
that have the same signature. Obviously, it cannot throw all of the checked
exceptions thrown by the other abstract methods that have the same signa-
ture because those throws clauses are in conflict. Regarded mathematically,
each abstract method involved throws a set of checked exceptions. The
checked exceptions that can be thrown by an implementation of those
abstract methods represent an intersection of those sets. If the intersection
is an empty set, then the implementation cannot throw any checked exceptions.
In other words, throws clauses conflicts can always be resolved by an imple-
mentation that simply does not throw a checked exception. Here is a slightly
more interesting example adapted from Bug Id 4042259:

class Test extends Superclass {


public void test() throws B { }
}

abstract class Superclass implements One, Two { }

interface One {
void test() throws A, C;
}

interface Two {
void test() throws B, C;

FIELDS AND METHODS 223


}

class A extends Exception {}


class B extends Exception {}
class C extends Exception {}

Attempting to compile the Test class generates the following compiler error:

Test.java:3: test() in Test cannot implement test() in


Superclass; overridden method does not throw B
public void test() throws B { }
^
1 error

This shows that the compiler enforces an entirely different rule for methods that
override more than one abstract method; the checked exceptions thrown
must be in the intersection of the throws clauses for the overridden meth-
ods.
Of course, the larger issue here is one of API design. If the abstract
methods are throwing different checked exceptions, they may not have the
same semantics. You want to carefully read the interface contracts of the
abstract methods involved. Providing a single implementation for
abstract methods that represent different behaviors would be a gross mis-
take. See also 3.8.3 Inheriting Methods With the Same Signature.

1.11.1.2 Covariant Result Types


The restriction that overriding and hiding method have the same result may be
lifted in a future release of the Java programming language. Allowing subclasses
to declare methods with the same signature as an otherwise inherited super-
class method, but with a narrower result type is referred to as a covariant
result type. At present, the following code does not compile.

import java.util.*;
class Superclass {
static long a() { return 0; }
Calendar b() { return new Calendar(); }
}
class Subclass extends Superclass {
static int a() { return 0; }

224 JAVA RULES


GregorianCalendar b() { return new GregorianCalendar(); }
}

Attempting to compile this code generates the following compiler errors:

Superclass.java:7: a() in Subclass cannot override a() in


Superclass; attempting to use incompatible return type
found : int
required: long
static int a() { return 0; }
^
Superclass.java:8: b() in Subclass cannot override b() in
Superclass; attempting to use incompatible return type
found : java.util.GregorianCalendar
required: Calendar
GregorianCalendar b() { return new GregorianCalendar(); }
^
2 errors

Such code will likely compile beginning with the same release that introduces
parameterized types. As of this writing, there are a total of 709 votes on the Bug
Parade for this RFE. The primary Bug Id is 4144488.

1.11.2 Overloaded Methods


Overloaded methods have the same name but different signatures. Further-
more, in the Java programming language (or any single-inheritance object-ori-
ented programming language for that matter), overloaded methods are by
definition members of the same class or interface. As stated in the JLS:
If two methods of a class (whether both declared in the same class, or
both inherited by a class, or one declared and one inherited) have the
same name but different signatures, then the method name is said to
be overloaded.143

Because constructors always have the same name as the class in which they are
declared, they are inherently overloaded.
Overloaded methods are not bound by the compiler-enforced method con-
tract. They can have different result types, different levels of access, and differ-

143. Gosling et al., The Java language Specification, §8.4.7, “Overloading.”

FIELDS AND METHODS 225


ent throws clauses. It is even possible for one overloaded method to be
declared static and another non-static. For example,

class Test {
public static void overloaded() { }
public String overloaded(String s) { return s; }
}

The fact that overloaded methods can have different result types actually comes
in very handy. There are numerous examples of overloaded methods in which
the result type corresponds to the parameter type(s). The overloaded methods
in the Math utility class are a very important example of this:

public static int round(float a)


public static long round(double a)

public static int abs(int a)


public static long abs(long a)
public static float abs(float a)
public static double abs(double a)

public static int min(int a, int b)


public static long min(long a, long b)
public static float min(float a, float b)
public static double min(double a, double b)

public static int max(int a, int b)


public static long max(long a, long b)
public static float max(float a, float b)
public static double max(double a, double b)

Before discussing the compiler-enforced method contract in relation to over-


loaded methods any further, however, we need to take a closer look at how
overloaded methods are used.
There is an important API design question that must be answered when con-
templating the use of overloaded methods. Should they be used to imple-
ment different behaviors? Consider the overloaded toBinaryString
methods in the following BitPattern utility class.

public class BitPattern {


private BitPattern() {} //utility class

226 JAVA RULES


static final long[] BIT_MASK = new long[64];
static {
for (int i = 0, length = BIT_MASK.length; i < length; i++)
BIT_MASK[i] = 1L << i;
}
public static String toBinaryString(int n) {
StringBuffer bitPattern = new StringBuffer(35);
for (int i = 31; i >= 0; i--) {
bitPattern.append((n & BIT_MASK[i]) == 0 ? '0':'1');
if (i % 8 == 0) bitPattern.append(" ");
}
return bitPattern.toString();
}
public static String toBinaryString(long n) {
StringBuffer bitPattern = new StringBuffer(67);
for (int i = 63; i >= 0; i--) {
bitPattern.append((n & BIT_MASK[i]) == 0 ? '0':'1');
if (i % 8 == 0) bitPattern.append(" ");
}
return bitPattern.toString();
}
public static String toBinaryString(float n) {
StringBuffer bitPattern = new StringBuffer(34);
int bits = Float.floatToIntBits(n);
for (int i = 31; i >= 0; i--) {
bitPattern.append((bits & BIT_MASK[i]) == 0 ? '0':'1');
if (i == 23 || i == 31) bitPattern.append(" ");
}
return bitPattern.toString();
}
public static String toBinaryString(double n) {
StringBuffer bitPattern = new StringBuffer(66);
long bits = Double.doubleToLongBits(n);
for (int i = 63; i >= 0; i--) {
bitPattern.append((bits & BIT_MASK[i])== 0 ? '0':'1');
if (i == 52 || i == 63) bitPattern.append(" ");
}
return bitPattern.toString();
}
}

There are four overloaded toBinaryString methods, one each for the int,
long, float, and double data types. All four methods do the same thing,

FIELDS AND METHODS 227


however. They print the bit pattern of a primitive data type. Are they the same
method in the mind of a client programmer? The method implementations are
obviously different, but the question is:

Do client programmers think about the fact that there are different
method implementations when invoking an overloaded method?

Another example is the overloaded print and println methods declared in


the PrintStream and PrintWriter classes. These are examples of fully
overloaded methods, meaning that any data type can be passed to them.144
There are 19 different (overloaded) implementations of these methods in the
PrintWriter class:

public void print(boolean b)


public void print(char c)
public void print(int i)
public void print(long l)
public void print(float f)
public void print(double d)
public void print(Object obj)
public void print(char[] s)
public void print(String s)

public void println()

public void println(boolean x)


public void println(char x)
public void println(int x)
public void println(long x)
public void println(float x)
public void println(double x)
public void println(Object x)
public void println(char[] x)
public void println(String x)

144. This term is of my own making. It is precisely defined in that an Object type parameter in
fully overloaded methods such as print and println can accept any reference type. On the
other hand, some fully overloaded methods include one or more array type parameters.

228 JAVA RULES


Fully overloaded methods such as these do not have to include byte or short
type parameters because of the method invocation conversion context (which I
refer to as built-in method overloading in this context), but they almost
always have a separate method for each of the four computational types (int,
long, float, and double, as well as char and boolean).
Ask yourself if you ever stop to think which of the print of println
methods you are invoking whenever you invoke System.out.print or
System.out.println. The answer is probably that you do not, unless
invoking System.out.println() to print a blank line. Otherwise, you just
want to print something. The fact is that overloaded methods are the same
method in the mind of a client programmer. The significance of this obser-
vation is that although not compiler enforced, you should respect the method
contract when declaring overloaded methods. Generally speaking, they should
all have the same result type (except when the result type corresponds to the
parameter types), be equally accessible, and have the same throws clause.
Don’t even think about declaring one overloaded method static and another
non-static. That is unheard of. Furthermore, overloaded methods should
implement the same behavior.

The implication is that overloaded methods share a common inter-


face contract, one based on the overloaded method name, not the
method signatures.

The selection of parameters in an overloaded method is sometimes based


on minor performance optimizations. In that sense, they are part of the imple-
mentation, not the interface. This is particularly true of fully overloaded methods
such as print and println (in the PrintStream and PrintWriter
classes and insert and append (in the StringBuffer class). Why
declare an overloaded method that accepts a String or char[] when there
has to be a method that accepts Object anyway? The answer is that the imple-
mentations for the more specific parameter types are slightly more efficient. At
the point at which you are overloading methods simply to improve the perfor-

FIELDS AND METHODS 229


mance of an implementation, the assumption that such methods implement the
same behavior is deeply ingrained.
You really do not want a client programmer thinking about parameter types
when invoking an overloaded method. Instead of trying to make it easy for a cli-
ent programmer to determine which overloaded method is invoked, as Bloch
does when he argues “never export two overloadings with the same number of
parameters,”145 you should think of parameters in overloaded methods as what
they are, the ability to pass multiple data types to what is essentially the
same method. For example, overloaded methods with only one parameter typi-
cally are capable of processing one of the following.
• Any integral data type
• Any floating-point data type
• Any primitive numeric data type
• Any primitive data type
• Any reference data type within a class hierarchy (baseclass)
• Any reference data type (Object)
• Any data type (a fully overloaded method)
In the BitPattern class, for example, the toBinaryString method is
overloaded to accept any primitive numeric data type. Client programmers never
have to stop and think about the parameter types of an overloaded method that
accepts any primitive numeric data type.
This brings me to Bloch’s “Item 26: Use overloading judiciously.” With all due
respect to a truly great programmer, I think his advice on this subject is really
messed up. He talks a lot about “confusing uses of overloading,”146 but every-
thing said in Item 26 is predicated on an example such as the following.

import java.util.*;
class Test {
public static void main(String[] args) {
List list = Arrays.asList(args);
ListIterator iterator = list.listIterator();

145. Bloch, Effective Java, “Item 26: Use overloading judiciously.”


146. Ibid.

230 JAVA RULES


while (iterator.hasNext())
classType(iterator.next());
}
static void classType(Object o) {
System.out.println("Object");
}
static void classType(String s) {
System.out.println("String");
}
}

If passed testing testing testing executing this program prints

Object
Object
Object

There is nothing confusing about this example. If the expectation was that it
would print String three times instead of Object , it’s just plain wrong. The
result type of the next() method in the Iterator interface is Object, so
the overloaded classType method invoked is always going to be the one that
has an Object type parameter. The transition from this rather obvious pro-
gramming mistake to a discussion of “confusing uses of overloading” is subtle
and illogical:
Because overriding is the norm and overloading is the exception, over-
riding sets people’s expectation for the behavior of method invocation.
As demonstrated by the CollectionClassifier example, over-
loading can easily confound these expectations. It is bad practice to
write code whose behavior would not be obvious to the average pro-
grammer upon inspection. This is especially true for APIs.147

The CollectionClassifier example (much like the one above) cannot


confound any expectations because it would never see the light of day. Further-
more, if the behavior is not obvious to the average programmer it is because it
is a totally bogus example that is designed to look like overriding instead of over-
loading. I dare say it would not take long for the average programmer to realize
that this example involves a rather serious programming mistake.

147. Ibid.

FIELDS AND METHODS 231


What is useful in Bloch’s approach to the subject is the remainder of the
paragraph just quoted:
If the typical user of an API does not know which of several method
overloadings will get invoked for a given set of parameters, use of the
API is likely to result in errors. These errors will likely manifest them-
selves as erratic behavior at run time, and many programmers will be
unable to diagnose them. Therefore you should avoid confusing uses
of overloading.148

If you are going to code overloaded methods, the one thing that you must do
above all else is to make sure that no matter what type of arguments are passed
that the correct thing will happen. In other words, client programmers should
never have to think about which overloaded method is invoked. That
Bloch is on a seriously bad tack in Item 26 can be seen in his advice to “never
export two overloadings with the same number of parameters.”149 What about
the common case of a fully overloaded methods such as println in the
PrintStream and PrintWriter classes? These classes export 19 differ-
ent methods with the same number of parameters. Are client programmers con-
fused by this? Oh contraire! Client programmers never have to think about which
fully overloaded method is invoked. And what about all the examples from the
Math utility class above? Are they confusing?
Perhaps the most important thing to remember about overloaded method
matching is that the choice of which method to invoke is made at compile time.
As stated in the JLS:
Java is designed to prevent additions to contracts and accidental name
collisions from breaking binary compatibility; specifically:

• Addition of more methods overloading a particular method


name does not break compatibility with preexisting binaries.
The method signature that the preexisting binary will use for
method lookup is chosen by Java's method overload resolu-
tion algorithm at compile time. (If Java had been designed so

148. Ibid.
149. Ibid.

232 JAVA RULES


that the particular method to be executed was chosen at run
time, then such an ambiguity might be detected at run time.
Such a rule would imply that adding an additional overloaded
method so as to make ambiguity possible at a call site could
break compatibility with an unknown number of preexisting
binaries…)150
The fact that adding a more specific method will not seen by pre-existing bina-
ries until they are recompiled is comparable to the problem of changeable
inlined constants. Elsewhere in the same chapter of the JLS we read:
Adding new methods that overload existing method names does not
break compatibility with pre-existing binaries. The method signature to
be used for each method invocation was determined when these exist-
ing binaries were compiled; therefore newly added methods will not be
used, even if their signatures are both applicable and more specific
than the method signature originally chosen.

While adding a new overloaded method or constructor may cause a


compile-time error the next time a class or interface is compiled
because there is no method or constructor that is most specific, no
such error occurs when a Java program is executed, because no over-
load resolution is done at execution time.151

The point is that one overloaded method cannot be used as more or less a
replacement for another. A naive programmer may think so because the newly
added method is more specific. If the older method is public, deleting it is a
mistake. In fact, deleting public methods is always a mistake in terms of
binary compatibility. It simply does not matter if the method is overloaded.

1.11.3 Overriding and Dynamic Method Lookup


If an accessible superclass instance method is not declared final, a subclass
instance method with the same signature is said to override the superclass
method.152 Methods declared private, static, or final cannot be over-
ridden for the following reasons.

150.Gosling et al., §13.2, “What Binary Compatibility Is and Is Not.”


151.Gosling et al., §13.4.21, “Method and Constructor Overloading.”

FIELDS AND METHODS 233


• Inaccessible methods (which includes all private methods) are not inher-
ited. They cannot therefore be overridden. Subclasses are free to declare a
method with the same name or method signature as an inaccessible super-
class instance method. In doing so the subclass programmer is not bound
by the compiler-enforced method contract.
• Class methods are hidden, not overridden.
• The final method modifier is used for the express purpose of preventing
subclasses from overriding superclass instance methods (or from hiding
superclass class methods). Note that all of the methods in a final class
are implicitly final because the class cannot be extended.
Methods in a final class cannot be overridden because there can be no sub-
classes. Such methods are usually said to be implicitly final.
In 5.4 Substitution is a Higher Concept than Polymorphism, I define polymor-
phism as distinct from overriding. Nevertheless, it is correct to refer to method
invocation expressions as being either polymorphic or not. For example, when
invoking instance methods, the primaryExpression.methodName gen-
eral form is polymorphic because the primary expression is subject to widening
reference conversions. To say that a method invocation expression is polymor-
phic implies that the method can be overridden and furthermore that dynamic
method lookup is used. This are sometimes referred to as a virtual method.153
Field access expressions are never polymorphic. As explained in the JLS:
This lack of dynamic lookup for field accesses allows Java to run effi-
ciently with straightforward implementations. The power of late binding
and overriding is available in, [sic] but only when instance methods are
used.154

152. Besides having the same method signature, I think it is a good idea to use the same parameter
names as an overridden superclass method. This is purely a matter of style, but it helps to make the
connection for client programmers reading the API docs. Method signatures are not what they see.
They see method headers that include the parameter names. If a client programmer is familiar with
the superclass parameter names, changing them in an overriding subclass method is a potential
source of confusion. For example, the String class declares equals(Object anObject) .
In the Object class the declaration is equals(Object obj). The difference is admittedly
small, but always gives me pause.
153. C programmers must use the virtual keyword for late binding and overriding function dec-
larations. Consequently, they use the term virtual functions when referring to functions that can be
overridden. The term virtual methods is nothing more than a throwback to C. I strongly discourage
this usage in Java.

234 JAVA RULES


Thus the only polymorphic expressions in the Java programming language are
method invocation expressions that invoke non-private instance methods.
If a method invocation is not polymorphic, the method invoked at runtime is
always the same as the compile-time declaration. For example,

class Test {
public static void main (String[] args) {
Superclass superclass = new Subclass();
superclass.print();
}
}
class Superclass {
static void print() {
System.out.println("superclass");
}
}
class Subclass extends Superclass {
static void print() {
System.out.println("subclass");
}
}

Executing this program prints superclass because the declared type of the
superclass variable is Superclass. The compiler always uses the
invokestatic machine instruction when invoking class methods, exactly as
if the method invocation expression had been written using the Type-
Name.methodName general form. Therefore the compile-time declaration is
always invoked.
When a non-private instance method is invoked the JVM searches the
method dispatch table that corresponds to the class of the object referenced
at run time. That may be the same class as the compile-time declaration or a
subclass thereof. For example,

class Test {
public static void main (String[] args) {
Superclass superclass = new Subclass();
superclass.print();

154. Gosling et al., §15.11.1, “Field Access Using a Primary.” Gilad Bracha cut a bunch of “in Java”
when working on the Second Edition. Here the “in” didn’t get cut.

FIELDS AND METHODS 235


}
}
class Superclass {
void print() {
System.out.println("superclass");
}
}
class Subclass extends Superclass {
void print() {
System.out.println("subclass");
}
}

This is the same as the previous example, only using instance methods. In both
cases, the compile-time declaration is found in Superclass. Executing this
program, however, prints subclass.
To fully understand overriding and dynamic method lookup you must know
something about invocation modes. Invocation modes determine whether or
not dynamic method lookup is used. There are five invocation modes defined in
the JLS: virtual, nonvirtual, super, static, and interface. It is
important to understand that invocation modes are conceptual only. They are
not written to class files.

Invocation modes are implicit in the bytecode (or machine


instruction) that a compiler uses to invoke a method.

The are four bytecodes (or machine instructions) that can be used to invoke
methods.155 The relationship between invocation modes and these four byte-
codes (or machine instructions) is an interesting one. Neither the JLS nor the
JVMS map invocation modes to machine instructions. In fact, there is not a sin-

155.Strictly speaking, the term bytecode refers only to the code arrays in a class file. There is a
code array for each method, including the special initialization methods. They are a series of one
byte opcodes usually followed by one or more operands. The opcodes are unsigned bytes with val-
ues ranging from zero to 255. For every opcode, the JVMS has a mnemonic such as zero and nop,
which is a “do nothing” machine instruction more commonly spelled no-op (for no operation). I tend
to use the term bytecode when referring to the numeric value of an opcode and machine instruction
when referring to the corresponding JVMS mnemonic. Note also that I use a fixed font for invocation
modes, bytecodes, and machine instructions, but that has no particular significance.

236 JAVA RULES


gle reference to invocation mode anywhere in either edition of the JVMS. Like-
wise, there is not a single mention of the four bytecodes (or machine
instructions) used to invoke methods anywhere in either edition of the JLS. The
relationship must be inferred from the following bulleted lists, each of which is
somewhat buried in their respective specification.
FROM THE JLS…

• If the compile-time declaration has the static modifier,


then the invocation mode is static.
• Otherwise, if the compile-time declaration has the private
modifier, then the invocation mode is nonvirtual.
• Otherwise, if the part of the method invocation before the left
parenthesis is of the form super. Identifier, then the invo-
cation mode is super.
• Otherwise, if the compile-time declaration is in an interface,
then the invocation mode is interface.
• Otherwise, the invocation mode is virtual.156
AND FROM THE JVMS…

• Invoke an instance method of an object, dispatching on the


(virtual) type of the object: invokevirtual. This is the normal
method dispatch in Java.
• Invoke a method that is implemented by an interface, search-
ing the methods implemented by the particular runtime
object to find the appropriate method: invokeinterface.
• Invoke an instance method requiring special handling, either
an instance initialization method <init>, a private
method, or a superclass method: invokespecial.
• Invoke a class (static) method in a named class: invoke-
static.157
It is easy enough to connect the dots. Table 1.10 maps invocation modes to
their corresponding machine instructions. As you can see, determining the invo-
cation mode is the same as deciding on which machine instruction to use. The
only reason the super invocation mode is different from nonvirtual is

156. Gosling et al., §15.12.3, “Compile-Time Step 3: Is the Chosen Method Appropriate?.”
157. Tim Lindholm and Frank Yellin, §3.11.8, “Method Invocation and Return Instructions.”

FIELDS AND METHODS 237


Table 1.10 Mapping Invocation Modes to Machine Instructions
Invocation Mode Bytecode JVMS Mnemonic

virtual 182 invokevirtual


nonvirtual 183 invokespeciala
super
static 184 invokestatic
interface 185 invokeinterface
a. The invokespecial instruction was named invokenonvirtual prior to the 1.0.2 release.

because a special ACC_SUPER flag must be set in the class file. The explana-
tion for this is of little interest to application programmers.158
Presumably this mapping from invocation modes to machine instructions is
only of interest to the software engineers who write compilers, but without the
JVMS application programmers would be left thinking that the super invocation
mode uses dynamic method lookup:
The strategy for method lookup depends on the invocation mode.

If the invocation mode is static, no target reference is needed and


overriding is not allowed.…

Otherwise, an instance method is to be invoked and there is a target


reference.… The other four possibilities for the invocation mode are
then considered.

If the invocation mode is nonvirtual, overriding is not allowed.…

Otherwise, the invocation mode is interface , virtual, or


super, and overriding may occur. A dynamic method lookup is
used.…159 [emphasis added]

158. Briefly, in the 1.0.2 release methods invoked using super were resolved at compile time,
which was a huge mistake. It is possible for the resolved method to be overridden in another super-
class that is compiled after the subclass in which method invocation appears is compiled. If the
ACC_SUPER flag is set (and it always is) the JVM looks in the method dispatch table of the direct
superclass (as it always should have). See Bug Id 4069324 for more details.
159. Gosling et al., §15.12.4.4, “Locate Method to Invoke.”

238 JAVA RULES


If the invocation mode is super, the JVM searches the method dispatch table of
the direct superclass. This is not really a dynamic method lookup in that it is not
based on the class of the object referenced. The notes for the invoke-
special machine instruction in the JVMS even say:
The difference between the invokespecial and the invokevirtual
instructions is that invokevirtual invokes a method based on the class
of the object. The invokespecial instruction is used to invoke instance
initialization methods (<init>) as well as private methods and
methods of a superclass of the current class.160

This specification in the JLS should be clarified. Even the statement that “overrid-
ing is possible” is confusing unless the reader is familiar with Bug Id 4069324.
Table 1.11 summarizes everything discussed so far. There is a sequence of
things to consider. At the end of that list, all remaining method invocation expres-
sions are virtual. You should take the time to compare this table to Table 1.7

Table 1.11 How to Determine the Invocation Mode


Dynamic
Invocation Method
Determining Factor Mode Lookup

B static methods and class initialization static No


methods (the <clinit> methods)

C private methods, instance initialization nonvirtual No


methods ( the <init> methods), and
constructors

D super.methodName general form super Noa

E The qualifying type is an interface type interface Yes

F ALL REMAINING INSTANCE METHODS virtual Yes

a. Keep in mind that this contradicts what the JLS says.

160. Tim Lindholm and Frank Yellin, under invokespecial in Chapter 6, “The Java Virtual
Machine Instruction Set.”

FIELDS AND METHODS 239


The Five General Forms and Qualifying Types on page 196. In two cases there is
a 1-to-1 correspondence. The super.methodName general form corresponds
to the super invocation mode, and the TypeName.methodName general
form corresponds to the static invocation mode. These general forms are not
polymorphic for somewhat obvious reasons: the super.methodName general
form is basically designed to defeat overriding; and TypeName.methodName
can only be used to invoke static methods. That leaves only the following gen-
eral forms of a method invocation expression.
• methodName
• this.MethodName
• primaryExpression.methodName
As stated above, the primaryExpression.methodName general form is
polymorphic, but only if the primary expression is either a variable name
or a method invocation expression. The other two kinds of primary expres-
sions are reference type literals and any use of the new keyword (class instance
creation expressions and array creation expressions). Non-private instance
methods in the String and Class classes (invoked when the primary expres-
sion is a string or class literal) are invoked using the invokevirtual
machine instruction, but they are not really polymorphic because both of these
classes are declared final. Constructors (invoked when the primary expres-
sion is a class instance or array creation expression) are not members of the
class. They are therefore not inherited and cannot be overridden. As can be
seen in Table 1.11, constructors are invoked using the invokespecial
machine instruction, which does not use dynamic method lookup.
The term polymorphic method invocation is thus reduced to non-
private instance methods invoked using one of the following general forms
of a method invocation expression.
• methodName
• this.MethodName
• primaryExpression.methodName (but only if the primary expres-
sion is either a variable name or a method invocation expression)

240 JAVA RULES


This is a short list and easily remembered. Application programmers should be
able to determine in a glance if a method invocation expression is polymorphic.

1.11.3.1 The invokeinterface machine instruction


Surprisingly little is said about how interface methods are invoked, which is curi-
ous because interface invocation mode is at least as important as the “the
normal method dispatch in Java.”157 Why? Because it is heavily used in a well-
designed API such as the Collections Framework that makes good use of inter-
faces. More precisely, the invokeinterface machine instruction to which it
maps in Table 1.10 is used whenever the qualifying type for a method invocation
is an interface type. For example,

class Test {
public static void main (String[] args) {
Runnable runnable = new Runnable() { public void run(){ } };
runnable.run();
runnable = new Thread();
runnable.run();
}
}

The decompiled code for the main method in the Test class is as follows:

Method void main(java.lang.String[])


0 new #2 <Class Test$1>
3 dup
4 invokespecial #3 <Method Test$1()>
7 astore_1
8 aload_1
9 invokeinterface (args 1) #4 <InterfaceMethod void run()>
14 new #5 <Class java.lang.Thread>
17 dup
18 invokespecial #6 <Method java.lang.Thread()>
21 astore_1
22 aload_1
23 invokeinterface (args 1) #4 <InterfaceMethod void run()>
28 return

The example shows the essential difference between invokeinterface and


invokevirtual. Java guarantees that the class type of an object assigned

FIELDS AND METHODS 241


to a class type variable will be either the same type or else a subclass. The class
type of an object assigned to an interface type variable is also guaranteed to be
assignment compatible, but in this case all that means is that the object imple-
ments the interface. In this example, both Test$1() (an anonymous class that
extends Object ) and Thread are assigned to the Runnable type variable.
These are unrelated class types. In both cases (the invokeinterface
and invokevirtual machine instructions), the method dispatch table
searched corresponds to the class of the object referenced at run time, so why
is a separate machine instruction required when the qualifying type is an inter-
face? I count this as one of the things truly difficult to understand.
As discussed in bottom half of 3.6.2 Implementation Inheritance starting on
page 352, method dispatch tables are always loaded starting from the Object
class and working down the class hierarchy. The key point to grasp in this con-
text is that overriding methods are loaded into the same row of a method dis-
patch table as the overridden method. In other words, the overridden method is
overlaid with a reference to the subclass method. Thus overridden and over-
riding methods are always loaded into the same row of related classes.
This has profound implications for runtime method dispatch. An implementation
that uses per-class method dispatch tables need only remember the index used
the first time a symbolic method reference is resolved. The same index value will
work for any of the different classes of object that can be substituted at run
time.
When the qualifying type is an interface, however, unrelated classes of
objects can be substituted at runtime. The interface method is therefore not
always going to be in the same row of the method dispatch table. It is some-
where in the method dispatch table for the class of the object referenced, just
not necessarily in the same row as the last time the interface method was
invoked. In early implementations, the invokeinterface_quick machine
instruction was used to address this problem. It used an extra byte that was
available in the corresponding invokeinterface instruction (the _quick
versions of machine instructions had to be the same length as the original
instruction they replaced) to store what was called a “guess” as to which row the
interface method would be found in the corresponding method dispatch table.

242 JAVA RULES


The “guess” was merely the row the interface method was found in the last time
it was invoked.161 As explained in the specification for the invokeinterface
instruction from the Second Edition of the JVMS:
The fourth operand byte exists to reserve space for an additional oper-
and used in certain of Sun's implementations, which replace the
invokeinterface instruction by a specialized pseudo-instruction at run
time. It must be retained for backwards compatibility.162

As stated above, the additional operand was the “guess.” The original JVMS
included an entire chapter on the _quick machine instructions. They are no
longer documented in part because of the Java Platform Debugger Architecture
obviates the need for Sun to make such implementation details publicly avail-
able, but also because new Sun implementations now use a more efficient algo-
rithm when searching for interface methods (the details of which have never
been made public, but to which Dr. Gosling alludes in a Bill Venner’s interview
quoted below).
I have always found this chapter in the unfolding Java platform story to be of
particular interest because anyone who has closely studied Dr. Gosling’s style of
coding will notice what I can only describe as an aversion to using interfaces. I
have always suspected this had something to do with the invokeinterface
machine instruction, and I am still pretty well convinced that it does. The prob-
lem with even mentioning this curiosity in a book about the Java programming
language is that the invokeinterface will be used to argue that interface
types are somehow inefficient. Here I am reminded of some quotes from “Item
37: Optimize judiciously” in Effective Java:

More computing sins are committed in the name of efficiency (with-


out necessarily achieving it) than for any other single reason—
including blind stupidity.163 —William A. Wulf

161. An implementation-defined “identifier” column was also used to uniquely identify each of the
interface methods in a method dispatch table. This alternative key was faster than a string compari-
son of method descriptors, and was used when guessing at the index value. The so-called “guess”
was correct if the identifier at that index value was the same as the one stored in the
invokeinterface_quick machine instruction.
162. Tim Lindholm and Frank Yellin, under invokeinterface in Chapter 6, “The Java Virtual
Machine Instruction Set.”

FIELDS AND METHODS 243


We should forget about small efficiencies, say about 97% of the
time: premature optimization is the root of all evil.164 —Donald E.
Knuth
We follow two rules in the matter of optimization:
Rule 1. Don’t do it.
Rule 2 (for experts only). Don’t do it yet—that is, not until you have a
perfectly clear and unoptimized solution.165 —M. A. Jackson

I would add that not using interfaces because the invokeinterface


machine instruction is slow could cost you your job. Nevertheless, the
invokeinterface does requires a few extra machine instructions to imple-
ment. Because the suggestion that using interface types could have perfor-
mance repercussions is so at odds with the Design Patterns principle of
“programming to an interface, not an implementation,”166 I have always wanted
to know what the language designers thought about this, and at one point even
sent an email to Frank Yellin (coauthor of the JVMS) asking his opinion on the
matter. Having not long before found a very obscure documentation error for
one of the machine instructions in the JVMS (for which I received a cherished
email response sent from Israel and that began with the exclamation “Abso-
lutely!”), I thought I could elicit a comment from him on this matter, but alas there
was never a response to that email. Then one day I heard the following in a 1999
JavaOne SYS-CON radio interview with Dr. Gosling in answer to a question about
interfaces [please keep in mind that Dr. Gosling is speaking extemporaneously in
front of a mike, and sounded somewhat weary of all the activity around him]:
…it turns out that there are some other interesting reasons for doing
interfaces that have to do with the way that method calls are imple-
mented, in that sort of under the sheets there are two forms of
dynamic dispatch, one that is very fast and one that is almost as fast.

163. William A. Wulf, A Case Against the GOTO. Proceedings of the 25th ACM National Confer-
ence 2 (1972): 791-797.
164. Donald E. Knuth, Structured Programming with go to Statements. Computing Surveys 6
(1974): 261-301.
165. M. A. Jackson, Principles of Program Design , (London, Academic Press, 1975).
166. Erich Gamma et al., §1.6, “How Design Patterns Solve Design Problems.”

244 JAVA RULES


And when you use an interface, you get the not quite so fast but much
more dynamic dispatch mechanism. And when you just call a class, you
get the three instructions, bang! You are there. It's fast as the proce-
dure call version.167

This was the first confirmation I ever had that this was a real issue, I mean that I
was reading the JVMS correctly. I was grateful for that, but then along came the
HotSpot VM and it was clear they had done something different when implement-
ing invokeinterface, but what and how much of a difference it made once
again had me on the lookout for some clue from a Sun source.
A Bill Veneer interview with Gosling provided enough of an answer for me to
put this issue to rest once and for all:
There is still part of me that says, maybe interfaces should never have
existed. People should just use classes for interfaces. But there turned
out to be some nice things that get done with interfaces that are differ-
ent. There's an interesting performance difference that most people
never think about, which is that interfaces need to do a kind of a
dynamic dispatch, whereas strict classes don't. The class model in
Java is really rather reactionary. It's almost exactly the original class
model from Simula 67. And one of the nice things about that model is
you can make method dispatch just fiendishly fast.

There are all kinds of tricks for doing interface-style dispatches, flexible
multiple-inherited dispatches, pretty fast. But they are always a couple
of instructions longer at least and maybe more, depending on how you
do it. Although there are techniques that trade it off against memory.
You can actually get it to the same performance as single inheritance,
if you are willing to basically spend more RAM building tables. But one
of the unsung nice things about the difference between classes and
interfaces is that it is statically knowable whether you can do dynamic
versus static dispatching.168

167. “JDJ EXCLUSIVE: An Interview with James Gosling,” (SYS-CON Publications, Inc., 1999),
www.sys-con.com/java/javaone/interviews/gosling.html.
168. Dr. James Gosling in an interview with Bill Venner entitled “A Conversation with James Gosling
(May 2001),” on the artima.com Web site (Artima Software, Inc.), www.artima.com/intv/
gosling314.html.

FIELDS AND METHODS 245


I interpret this to mean the HotSpot VM has all but closed the performance gap
between invokeinterface and invokevirtual. A microbenchmark
test could be written to test that assumption, but to what end? Are you going to
stop using interfaces if there is still a measurable difference? I think not.

1.12 Method Forwarding


Method forwarding is not unlike having your mail forwarded to a new address.
For example,

public int compareTo(Object o) {


return compareTo((String)o);
}

This is the actual compareTo(Object o) in the String class. Note that if


o is not an instance of String, this implementation will throw a ClassCast-
Exception , which is precisely what should happen.
This is a very common reason for using method forwarding in the core API.
Prior to the introduction of the Collections Framework and the Comparable
interface in the 1.2 release, a number of classes in the core API declared
compareTo methods such as compareTo(String anotherString) in
the String class. Bloch used this de facto naming convention when declaring
compareTo(Object o) in the Comparable interface. The narrower param-
eter types in existing methods such as compareTo(String another-
String) meant they would not be applicable when compareTo(Object o)
is invoked by the various sort methods and constructors in the Collections
Framework. Those existing methods could not be deleted without breaking com-
patibility with existing binaries, so the question was what to do? Faced with the
necessity of implementing the Comparable interface, many of the core API
classes used method forwarding.
Method forwarding has also been used in the core API to solve a very subtle
problem with overloaded method matching in pre 1.4.2 releases. See 5.8.2 The
Declaring Class of Applicable Methods for a discussion.

246 JAVA RULES


Chapter 2

Scope, Shadowing, and


Qualified Access

Chapter Contents
2.1 Introduction 248
2.2 Namespaces 249
2.2.1 The Meaning of a Simple or Partially Qualified Name 252
2.2.2 Disambiguating Type Names 254
2.3 The Fundamentals of Lexical Scoping 258
2.3.1 The Mysterious Scope Hole 266
2.3.2 Compilation Unit Scope 268
2.3.3 Members Shadow Declarations in Enclosing Scopes 268
2.3.4 The Scope of Types in the Unnamed Package 271
2.3.5 Circular Dependencies in Type Declarations 276
2.4 Shadowing 278
2.5 Obscuring 281
2.6 Observable Compilation Units and Packages 284
2.7 Qualified Access 286
2.8 Access Control 290
2.8.1 The protected Access Modifier 301
2.8.2 Full Access to the Members of an Enclosing Class 312
2.8.3 Members More Accessible Than Their Class Type 315
2.8.4 Accessing the Implementation of Same Class Objects 323
2.9 Encapsulation 324

SCOPE, SHADOWING, AND QUALIFIED ACCESS 247


2.1 Introduction
The single most important lesson one can learn from this chapter is that the def-
inition of the terms scope, shadowing, and qualified access are so closely
related that they are properly regarded as different aspects of the same subject.
The definition of scope is simple (no pun intended). Anywhere the simple name
of an entity can be used that entity is said to be in scope. How does one refer-
ence an entity that is out of scope? The answer is by using a qualified name,
field access expression, or method invocation expression. These different
“means of access”1 define qualified access. As stated in the JLS:
All three are syntactically similar in that a “.” token appears, preceded
by some indication of a package, type, or expression having a type and
followed by an Identifier that names a member of the package or
type. These are collectively known as constructs for qualified
access.2
Scope and qualified access are thus two halves of a whole. Where one stops,
the other begins. The JLS alludes to this in the following definition of access:
Access is a different concept from scope; access specifies the part of
the program text within which the declared entity can be referred to by
a qualified name, a field access expression, or a method invocation
expression in which the method is not specified by a simple name.3

How does shadowing fit in? Shadowing is an exception to the rule. A shadowed
declaration is by definition in scope, but cannot be referenced using a simple
name.
More generally, every (Java programming language) entity that exists on the
planet, or that ever will exist for that matter, is one of the following:
• In scope and visible
• In scope, but shadowed or obscured
• Accessible

1. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java Language Specification, Sec-
ond Edition, (Boston: Addison-Wesley, 2000), §6.6, “Access Control.”
2. Ibid.
3. Gosling et al., introduction to Chapter 6, “Names.”

248 JAVA RULES


• Inaccessible
• Unobservable
By the end of this chapter you will know precisely what each of these means.
If you are wondering why I keep using the term shadowing instead of hid-
ing then you are unaware of an important usage distinction that Gilad Bracha
introduced in the Second Edition of the JLS:
Note that shadowing is distinct from hiding. Hiding, in the technical
sense defined in this specification, applies only to members which
would otherwise be inherited but are not because of a declaration in a
subclass. Shadowing is also distinct from obscuring.4

I have always maintained this very same distinction in Java Rules (even before
the Second Edition of the JLS was released), and was glad to see Bracha intro-
duce this new terminology to support the distinction. It is by no means super-
fluous. Bracha continues to use the term hiding in the context of members that
are not inherited because that is what most programmers understood hiding to
mean. He uses the new terms shadowing and obscuring for the more refined
meanings of what used to be lumped together as hiding. Note also that the term
visible is now defined in terms of shadowing, not hiding. A name that is shad-
owed in not visible, whereas an entity that is hidden is not inherited and therefore
not in scope. Only fields, class methods, and nested types (members of a class
or interface type) are hidden. That about sums up the change. Obscuring is also
discussed in this chapter for the sake of completeness.

2.2 Namespaces
The concept of a namespace is not difficult to master. A family is a namespace
in which each individual must have a unique first name. A person’s full name is
like the fully qualified name of an entity in the Java programming language,
except that the syntax is different. Instead of writing John Doe, in Java you write
Doe.John. In this analogy, the family name corresponds to a package name,
and the first name corresponds to a class or interface type. A package is a group
of logically (versus biologically) related classes and interfaces, each of which

4. Ibid., §6.3.1, “Shadowing.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 249


must have a unique name. Classes and interfaces are even said to be members
of a package, just like saying that John is a member of the Doe family.
There are a total of five namespaces in the Java programming language:
1. The system namespace (as defined by the bootstrap class loader)
2. An application, applet, or other “user-defined” class loader namespace
3. Package namespace
4. Class or interface type namespace
5. Block namespace
These namespaces are listed in the order of their size, from the largest name-
space (the system namespace) to the smallest (a block). With the exception of
application, applet, and other user-defined class loader namespaces, smaller
namespaces are contained in larger namespaces as depicted in Figure 2.1.

Figure 2.1 Namespaces

The system namespace represents a “a common set of classes”5 loaded


by the bootstrap class loader and otherwise known as the core API, J2SE, J2EE,
etc. (Note that the bootstrap class loader is also known as the system class

250 JAVA RULES


loader.) The system namespace is dominated by the java and javax top-
level packages, but types from other packages can be loaded into the system
namespace using the bootstrap (or system) class loader.
The application namespace is created by the java launcher using a spe-
cial instance of the ClassLoader class known as the application class
loader. Likewise, applet namespaces are created by browsers using a special
instance of the ClassLoader class that is usually named
AppletClassLoader. Although special, the application and applet class
loaders are instances of the ClassLoader class and therefore considered to
be “user-defined” class loaders. (In this case, the “user” is the java launcher or
a browser.) The significance of the application, applet, and other user-defined
class loader namespaces is discussed in 1.4.3 The Importance of Unique Pack-
age Names in the Second Edition of Volume 1.
This section discusses the package, type, and block namespaces, which
correspond to package, type, and block scope as shown in Table 2.1 Lexical
Scoping on page 262. The most important of these is the package namespace.
This explains why the term namespace used by itself is usually a reference to
packages. For example, the unnamed package is also known as the unnamed
namespace, and named packages are sometimes referred to as named
namespaces. In fact, the term namespace is sometimes used as a synonym
for package, usually in reference to a top-level package. For example, the java
package is sometimes referred to as the java namespace.
The members of a package (classes, interfaces, and subpackages) must
have unique names. You cannot fully understand what makes a package a name-
space unless you know that simple and partially qualified names exist only in
source code. Inside of the class files generated by a compiler all names are fully
qualified, which means they include a package name. Fully qualified names are
discussed further in the next section.

5. Sheng Liang and Gilad Bracha, Dynamic Class Loading in the Java Virtual Machine, Pro-
ceedings of the 1998 ACM SIGPLAN Conference on Object-Oriented Programming Systems, Lan-
guages and Applications (OOPSLA’98), citeseer.nj.nec.com/liang98dynamic.html. This
is a must read for anyone wanting to fully understand dynamic class loading.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 251


Types are namespaces within a package namespace. The members of a
class or interface type must have unique names. However, because of the syn-
tactic classification of names according to context (discussed in 1.4.1 The Syn-
tactic Classification of Names According to Context in the Second Edition of
Volume 1), the class or interface “namespace” is actually carved into four sepa-
rate namespaces: one for types, one for methods, one for fields, and one for
labels. Note that the uniqueness of a method name is actually based on the
method signature, not the simple name.
Blocks introduce yet another namespace. As explained in 1.7 Local Vari-
ables, local variable and parameter names are not actually written to class files.
Nevertheless, they must have unique indices. The reason why the fields in a
block class can shadow local variable and parameter names is simply the values
assigned to them are not stored in the local variable array.

2.2.1 The Meaning of a Simple or Partially Qualified Name


Before discussing the meaning of a simple or partially qualified name, the differ-
ence between a qualified name and a field access or method invocation expres-
sion must be understood. I scoffed at this usage distinction in the JLS for years
until finally coming to appreciate why it is necessary. A field access or method
invocation expression involving a non-static member (an instance variable or
instance method, respectively) is not a qualified name because the expression
to the left of the period separator is a reference to an object. For example,
Dog dog = new Dog();
dog.bark();

That dog.bark() is a method invocation expression and not a qualified name


is rather obvious. Likewise, array.length is a field access expression, not a
qualified name. Both dog and array are references to objects. Such field
access and method invocation expressions have the appearance of qualified
names, but they are not really names at all. In fact, the JLS is careful to always

Any discussion of qualified names always implies either a type name


or a static member.

252 JAVA RULES


refer to the field or method name as an Identifier (which is usually capitalized).
The problem is field access expressions in the TypeName.fieldName gen-
eral form used to invoke class methods. For example, Integer.MAX_VALUE.
Why is this a qualified name instead of a field access expression? The
answer is that the expression to the left of the dot separator is a type name, not
a reference to an object. There is no object to “access.”
By the time I came around to appreciate what now seems like a rather obvi-
ous usage distinction, I had already fully developed 1.10 The Five General
Forms. Strictly speaking, the TypeName.fieldName general form is a quali-
fied name (not a field access expression). I purposefully blur this distinction in
Java Rules in order to discuss “the five general forms” of field access and
method invocation expressions. The JLS does as much by clearly defining any
use of a method name as a method invocation expression while at the same time
consistently referring to the TypeName.methodName general form as a
qualified name when used to invoke a hidden class method.
Names are either simple or qualified. A simple name is the same as the
identifier used in the declaration of an entity. A qualified name includes one or
more “dot” separators (a period or full stop). A partially qualified includes the
name of an enclosing class or interface type, which may be the class or inter-
face in which the entity is declared. If the type name is further qualified by a
package name, then the name is said to be fully qualified. Note that if the
entity referenced is a member of a nested type, there may be more than type
name in a partially or fully qualified name. Simple and partially qualified
names exist only in source code. After compilation, all of the names in a class
file are fully qualified (except, of course, local variable and parameter names
which are converted into indices).
It is a requirement of the class file format that all names (referred to as sym-
bolic references in a class file) are fully qualified. As stated in the JVMS:
Class names that appear in class file structures are always repre-
sented in a fully qualified form.6

6. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, Second Edition,
(Boston: Addison-Wesley, 1999), §4.2, “Internal Form of Fully Qualified Class Names.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 253


Compilers therefore must determine the meaning of simple and partially qualified
names. The meaning a simple or partially qualified name is the fully qual-
ified name. As stated in the JLS, the cannot resolve symbol error mes-
sage discussed in 1.10 The Five General Forms is generated “if [a] name has no
meaning.”7
Determining the meaning of a name is surprisingly complex. In addition to
everything discussed in this chapter, it involves the syntactic classification of
names according to context, a special naming restriction for package members,
the simple and complex classpath formulas (all of which are discussed in Volume
1), as well as Table 1.7 The Five General Forms and Qualifying Types on page
196, 1.10.1 The Meaning of a Simple Field or Method Name, 1.10.3 Casting a
Target Reference, 1.11 Method Signatures (and subsections thereof), 3.2 Hid-
ing, and 3.8.2 Ambiguous Names Related to Inheritance in this volume. This is a
formidable list, and I am sure that it is less than complete. I poke fun at the soft-
ware engineers who write compilers such as javac by referring to the “arcane
world of compilers” elsewhere in Java Rules, but compilers are clearly some of
the most sophisticated programs on the planet.

2.2.2 Disambiguating Type Names


The problem of ambiguous type names arises from the fact that both compila-
tion unit scope (read import declarations) and package scope are both
defined in terms of one or more compilation units. As can be seen in Table 2.1
Lexical Scoping on page 262, to say that a class or interface type that is a pack-
age member scopes to a package actually means that it scopes to all of the
compilation units that belong to that package. Imported types scope to the
same compilation units. It is therefore possible for a package member and an
imported type to scope to the same compilation unit. In the words of one of my
all-time favorite movies, however, “there can be only one” simple type name in
scope in a given compilation unit. This section discusses two special rules used

7. Gosling et al., §6.5, “Determining the Meaning of a Name.”

254 JAVA RULES


to disambiguate type names under these circumstances. If neither rule applies,
an ambiguous type name compiler error is generated.
There are five steps involved in disambiguating type names. The first two
steps are normal lexical scoping rules (using the “throwing a pebble into a pond”
analogy in 2.3 The Fundamentals of Lexical Scoping). Steps three and four are
the special rules that must be memorized. The last step describes when (the
more common) ambiguous type name compiler error is generated:
1. Block Scope: A local class with the same name that is both in scope and
visible is always used first.
2. Type Scope: Next a member type that is both in scope and visible is used. If
more than one member type with the same simple name is in scope and vis-
ible, the type name is ambiguous. This rarely happens in practice. Much of
the ambiguity that could have arisen as the result of introducing nested
types in the 1.1 release was eliminated by the rule discussed in 2.3.3
Members Shadow Declarations in Enclosing Scopes. For member types
to be ambiguous, one of them therefore must be inherited from a direct
superinterface. See 3.8.2 Ambiguous Names Related to Inheritance for an
example.
3. Compilation Unit or Package Scope: If there is no local or member type
with the same name that is both in scope and visible, a type declared in the
same compilation unit or imported using a single-type-import declaration is
used. Note that the imported type may be either a package member or
nested type.
4. Package Scope: Before using type-import-on-demand declarations, a type
declared in another compilation unit of the same package is used.
5. Compilation Unit Scope: Finally, type-import-on-demand declarations are
used. If more than one type with the same name is imported using type-
import-on-demand declarations, the type name is ambiguous.
Single-type-import declarations are on a par with types declared in the same
compilation because of a special compiler rule that eliminates any possibility of
ambiguity between the two: a single-type-import declaration cannot be used to
import a type with the same simple name as a package member declared in
the same compilation unit, nor can two different types with the same simple

SCOPE, SHADOWING, AND QUALIFIED ACCESS 255


name be imported using single-type-import declarations. (In both cases you
would be declaring two different types to have the same simple name.)
The rationale behind the last three steps is explained as follows in the JLS.
This order of considering type declarations is designed to choose the
most explicit of two or more applicable type declarations. 8

Types imported using single-type-import declarations shadow members of the


same package declared in other compilation units. They are considered more
explicit because the programmer more or less says so by using a single-
type-import declaration. If a type is neither declared in the current compila-
tion unit nor imported using a single-type-import declaration, the compiler
assumes that the programmer is referencing a type declared in a different com-
pilation unit of the same package. That is, a type that has package scope is con-
sidered more explicit than one imported using a type-import-on-demand
declaration. Whenever these rules for disambiguating type names do not work
the way you want, a single-type-import declaration can be used to solve the prob-
lem. The other alternative is to use fully qualified names.
It is not possible to disambiguate type names imported using type-import-on-
demand declarations. A Java compiler must therefore search all of the packages
imported using type-import-on-demand declarations. It does not stop after find-
ing a matching type name because the possibility exists that another package
imported using a type-import-on-demand declaration will have a type with the
same simple name. For example,

import java.awt.*;
import java.util.*;
class Test {
List l;
}

Attempting to compile this program generates the following compiler error:

Test.java:4: reference to List is ambiguous, both class


java.util.List in java.util and class java.awt.List in java.awt

8. Gosling et al., §6.5.5.1, “Simple Type Names.”

256 JAVA RULES


match
List l;
^
1 error

Both of these packages include a type named List. The compiler cannot arbi-
trarily decide which type-import-on-demand declaration you intended to use,
which is exactly what it would be doing if the search stopped as soon as a
matching type name was found.
Assuming that there is no package statement at the top of a compilation
unit, these rules stipulate that the unnamed package is searched before
named packages imported using a type-import-on-demand declaration.
This is a language feature that sooner or later comes to the attention of most
Java programmers when they inadvertently declare a class that has the same
simple name as a class in a named package, including notably the core API. If
that class is stored in the unnamed package, it is only a matter of time before
you try to compile a program that attempts to import a like-named class from a
named package using a type-import-on-demand declaration. In my case I
thoughtlessly created a Properties class that sat in the unnamed package
for months (like a time bomb waiting to explode) until one day when I tried
importing java.util.Properties using import java.util.*. All
sorts of compiler errors were generated because I was trying to use my
Properties class as if it were java.util.Properties. This is a very
hard problem to diagnose the first time you encounter it.
As stated above, the first two steps in the process of disambiguating type
names are normal lexical scoping rules. For example,

class Test {
class Widget { } // inner member class
public static void main(String[] args) {
class Widget { } // local class
System.out.println(Widget.class);
}
}
class Widget { } // helper class

Executing this program prints

SCOPE, SHADOWING, AND QUALIFIED ACCESS 257


class Test$1$Widget

Test$1$Widget is the binary name (referred to as bytecode names in the


Inner Classes Specification) of the local class.9 Now reverse the order of the
two statements in the main method:

public static void main(String[] args) {


System.out.println(Widget.class);
class Widget { }
}

The program now prints

class Test$Widget

This is the binary name of the inner member class. The local class is no longer in
scope because it has not yet been declared. In neither case does the name
mean Widget, which is the helper class.

2.3 The Fundamentals of Lexical Scoping


Scope has two fundamentally different meanings. As defined in the JLS, scope
determines when simple names can be used:
Every declaration that introduces a name has a scope, which is the
part of the program text within which the declared entity can be
referred to by a simple name.10

Notice that declarations, not names, have a scope. In fact, Gilad Bracha
changed the name of the section in which scope is defined from 6.3 Scope of a
Simple Name to 6.3 Scope of a Declaration. Although the JLS says declarations
have a scope, I prefer the term entity rather than declaration. For example,
programmers must know if an entity is in scope or out of scope in order to
know if the simple name can be used. If the entity is out of scope, then some
form of qualified access must be used.

9. John Rose, Inner Classes Specification (Mountain View: Sun Microsystems, 1997), “What are
the new binary compatibility requirements for Java 1.1 classes?”
10. Gosling et al., introduction to Chapter 6, “Names.”

258 JAVA RULES


The problem I have with this definition of scope is that local variables and
parameters do not really have a simple name (a term which implies membership
and the existence of a qualified name). Therefore scope cannot be defined in
terms of using the simple name of a local variable or parameter. What then is the
meaning of scope as applied to local variables and parameters? The scope of a
local variable or parameter coincides with the lifetime of those variables.
The difference then is between using the simple name of a member versus
being able to name a local variable or parameter at all.
As explained in 1.7 Local Variables, local variables and parameters are not
really created and destroyed. They are just values stored in the local variable
array of an activation frame. Thus scope is the only terminological framework in
which to discuss the lifetime of a local variable or parameter. For many program-
mers, the scope of a local variable or parameter is synonymous with the lifetime
of those variables, and the JLS should not ignore this reality when defining the
term scope. It is important, however, not to infer a causal relationship
between scope and the lifetime of a local variable or parameter. The
scope and lifetime of local variables or parameters are coterminous, but these
are still very different concepts. Other entities continue to exist when they are
out of scope.11
There are some programming languages in which the scope of a declaration
is determined at run time. Such languages are said to be dynamically scoped.
Java, however, like C and C++ is statically scoped, or what is more commonly
referred to as a lexically scoped language. Though not used in either the JLS
or the JVMS, the more common term is lexical scoping. For example, John
Rose refers to “the new regularity in lexical scoping”12 in the Inner Classes

11. Local classes are interesting in this regard. Do they continue to exist when out of scope? The
answer is Yes (as class files that are dynamically loaded). Given the five choices of “in scope and
visible,” “in scope, but shadowed or obscured,” “accessible,” “inaccessible,” or “unobservable” from
the introduction to this chapter, we say that local and anonymous classes are implicitly private
and therefore inaccessible outside of the block in which they are declared (which for anonymous
classes created in the variable initializer of a field is the body of an <init> of <clinit>
method). Thus there exists an entity that cannot be named except when in scope. In that sense, they
are comparable to local variables and parameters.
12. John Rose, “How does the Java Language Specification change for inner classes?”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 259


Specification. Many Java books discuss scope without ever using the term lex-

As used in the Java programming language, lexical scoping is sim-


ply a more formal term for scope.

ical scoping. You run into the classic problem of trying to describe water to a
fish when explaining the term lexical scoping to someone who has never writ-
ten programs in a dynamically scoped language. Nevertheless, all Java program-
mers should at least be aware of the fact that Java is a lexically scoped
language.
Lexical scoping can be compared to throwing a pebble into a pond. The
compiler begins searching for the declaration of an entity at the point at which a
simple or partially qualified name is used and works outward. Hence, smaller
scopes such as block scope are always searched before the larger scopes such
as a compilation unit or package. There are five scopes in the Java program-
ming language. They are listed here in the order of the size:
• Host system scope
• Package scope
• Compilation unit scope
• Type scope 13
• Block scope (which includes methods, constructors,
and initialization blocks)
If the simple name pebble were used, you can look at this list as representing
the concentric circles made by the pebble as the compiler searches for a decla-
ration with the same name.
All entities scope to one of these lexical constructs. The complete list of enti-
ties in the Java programming language are repeated here for your convenience.

13. I prefer the term type scope over class scope. Otherwise, you must say that interface con-
stants are in scope in their own variable initializers and well as in the variable initializers of other
interface constants declared textually after them in a given interface type because they have what?
Class scope? No, the answer is because interface constants have type scope.

260 JAVA RULES


• Package
• Class
• Interface
• Method
• Constructor
• Field
• Local variable
• Parameter
Constructors are removed from consideration because although they are
“invoked by class instance creation expressions,”14 the name in a class instance
creation expression is considered to be a class or interface type name.15
Classes and interfaces are crossed out because the five kinds of classes and
interfaces (also repeated here for your convenience) have different scopes:
• Package members
• Nested top-level classes and interfaces
• Inner member classes
• Local classes
• Anonymous classes
Anonymous classes are removed from consideration because they do not have
names. All member types scope to the class or interface in which they are
declared, so there is no deference between nested top-level classes and inter-
faces and inner member classes as far as lexical scoping is concerned. All three
are referred to as member types in this discussion. To this list must be added
imported types, which scope to the compilation unit in which the import decla-
ration appears. They must therefore be regarded separately from all other
types.
If you do the arithmetic, this means there are nine different kinds of entities
that must be considered in any discussion of scope. Table 2.1 includes all nine

14. Gosling et al., §8.8, “Constructor Declarations.”


15. Gosling et al., §15.9, “Class Instance Creation Expressions.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 261


of them and the lexical construct to which they scope. Assuming the declara-

Table 2.1 Lexical Scoping

Lexical Construct

Package Scope (any compilation unit


Host System Scope (any observable

that belongs to the same package)

Class or Interface Type Scope


Compilation Unit Scope
compilation units)

Block Scope
Entity

Packages ✔
Package members ✔
Imported types ✔
Fields, methods, and member types ✔
Local classes, local variables, and parameters ✔

tions are not shadowed, this table can be stated in plain English (including a few
more details) as follows.
• Observable package names can be used anywhere.
• The simple name of a package member can be used anywhere in any of the
compilation units that belong to that package.
• The simple name of an imported type can be used anywhere in the compila-
tion unit in which the import declaration appears. Although member types
only have type scope, they can be imported. The effect of doing so is to

262 JAVA RULES


give the simple name of a member type compilation unit scope. This is actu-
ally quite extraordinary. It is the only time that the simple name of a member
can be used outside of the class or interface in which it is declared. Note
that a member type cannot be imported unless the class or interface in
which it is declared is public. See also 2.5.3 Importing Nested Classes of
All Sorts (Top-Level or Inner) in Volume 1.
• The simple name of a field, method, or member type can be used anywhere
in the class or interface type in which it is either declared or inherited.
• The name of a local class can be used anywhere to the right of the local
class declaration statement in the block in which the declaration appears
(the immediately enclosing block).
• The name of a local variable can be used in its own variable initializer
(though the rules of definite assignment apply), in any declarators to the
right if its own variable initializer, and anywhere to the right of the local vari-
able declaration statement in the remainder of the block in which the decla-
ration appears (the immediately enclosing block). There is a special case
for local variables declared in the ForInit part of a for statement header.
The name of these loop variables can be used in any statement to the right
of the local variable declaration statement in the for statement header as
well as anywhere in the contained statement or statement block.
• The name of method and constructor parameters can be used anywhere in
the method or constructor body.
• The name of an exception handler parameter can be used anywhere in the
catch block.
My presentation of scope is slightly different than the JLS which says that the
scope of a package member or imported type does not include package and
import declarations. For example,
The scope of a top level type is all type declarations in the package in
which the top level type is declared.16

Though this specification does not explicitly exclude package and import
declarations, 7.5 Import Declarations does:

16. Gosling et al., §6.3, “Scope of a Declaration.” This sections says: “The scoping rules for various
constructs are given in the sections that describe those constructs. For convenience, the rules are
repeated here,” but I cannot find the scope of a class type discussed anywhere else in the JLS.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 263


The scope of a type imported by a single-type-import declaration or
type-import-on-demand declaration is all the class and interface type
declarations in the compilation unit in which the import declaration
appears.

An import declaration makes types available by their simple names


only within the compilation unit that actually contains the import decla-
ration. The scope of the entities(s) [sic] it introduces specifically
does not include the package statement, other import decla-
rations in the current compilation unit, or other compilation units in
the same package.17 [emphasis added]

What is the point in this? The entity named in a package statement is a fully
qualified package name. While single-type-import declaration do name individual
class or interface types, the JLS is clear that a fully qualified name (or, more pre-
cisely, the canonical name) is required:
A single-type-import declaration imports a single named type, by men-
tioning its canonical name.18

If import declarations must “mention” the canonical name of a class or inter-


face, why not say that package members or imported types scope to a compila-
tion unit? The only real issue here is Bug Id 4361575, which concerns importing
members of the unnamed package and is discussed in 2.3.4 The Scope of
Types in the Unnamed Package.
I have deliberately written the rules in the above list so that each one
includes the word anywhere. The precise meaning of in scope and out of
scope is literally in or out of the lexical construct to which the entity scopes.
Generally speaking, the simple name of an entity can be used anywhere in the
lexical construct to which it scopes. For example, fields scope to types. Thus
statements to the effect that a field is either in scope or out of scope apply to
the entire body of the class or interface type to which they scope. You may say
that a field is in scope in a method, but the same would be true for every other
method in the same class because fields scope to types, not to methods. Like-

17. Gosling et al., §7.5, “Import Declarations.”


18. Ibid.

264 JAVA RULES


wise, a method parameter scopes to a method body. The number of local or
anonymous classes, arbitrary blocks, for loops, or the like in a method body
does not matter. A method parameter is in scope anywhere in the method
body. Even when a field, method parameter, or other entity is shadowed, it is still
technically in scope.
The term scope used by itself refers to one of the lexical constructs in Table
2.1. These lexical constructs are referred to as a “part of the program text” in
the JLS definition of scope at the beginning of this section. Elsewhere, the JLS
uses the term region:
The scope of a declaration is the region of the program within which
the entity declared by the declaration can be referred to using a simple
name (provided it is visible).19

I prefer lexical construct.20 This use of the term scope (as a noun) is analogous
to namespace. This gives rise to the term enclosing scope as in the following
quote from the Inner Classes Specification:
The code of an inner class can use simple names from enclosing
scopes, including both class and instance members of enclosing
classes, and local variables of enclosing blocks.21

The term enclosing scope is useful because of inner classes. However, soft-
ware engineers and technical writers freely combine any number of adjectives
with the term scope. Here are some examples I have found in other Java books:
• Current scope
• Outer scope
• Lower scope
• Temporary scope
• Intervening scope

19. Gosling et al., §6.3, “Scope of a Declaration.”


20. Just as I prefer “mechanism” in other contexts. I think this has something to do with the fact that
my first “real” job (after dropping out of college and doing a stint in the Army) was as a construction
worker. (Somehow I think I’ll regret saying this.)
21. Rose, “What are top-level classes and inner classes?”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 265


The resulting proliferation of terms is confusing, and none of them are used in
the JLS. However, I should point out that John Rose used “intervening scope”
once while writing the Inner Classes Specification.
Some of the finer points of lexical scoping are discussed in the following sub-
sections. As usual, I try to list the subsections in their order of importance.

2.3.1 The Mysterious Scope Hole


Inside of the nested top-level classes and interfaces in a containment hierarchy,
only the static members of an enclosing class or interface type are in scope.
The non-static members (inner classes, instance variables, and instance
methods) are not in scope. The name of this section is derived from an email
sent to me by no less than John Rose, who was very generous in answering
detailed questions I had about the Inner Classes Specification shortly after it
was published. An excerpt from that email in which I first heard the term scope
hole follows.
One other thing: The “static” keyword in Java introduces a “scope
hole,” in that it prevents use of non-static names from enclosing
scopes. (Under pure lexical scoping, those names would have the
same meaning everywhere, including within the construct marked
“static.”) Though such “scope holes” are flaws in lexical scoping, they
are not gratuitous in Java, but simply an unavoidable implication of the
meaning of “static.” 22

Contrast this with the following specifications from the Second Edition of the JLS
(which superseded the Inner Classes Specification).
The scope of a declaration of a member m declared in or inherited by a
class type C is the entire body of C, including any nested type declara-
tions.23 [from JLS 8.1.5 Class Body and Member Declarations]

The scope of the declaration of a member m declared in or inherited by


an interface type I is the entire body of I, including any nested type

22. John Rose, author of the Inner Classes Specification, in a personal email dated 7 February
1998.
23. Gosling et al., §8.1.5, “Class Body and Member Declarations.”

266 JAVA RULES


declarations.24 [from JLS 9.1.3 Interface Body and Member Declara-
tions]

In both cases, the blanket statement “including any nested type declarations” is
incorrect. For example,

class C {
String m = "Where oh where has my little String gone? " +
"Where oh where can it be?";
static class ScopeHole {
void print() {
System.out.println(m);
}
}
}

Attempting to compile this class generates the following compiler error:

C.java:6: non-static variable m cannot be referenced from a


static context
System.out.println(m);
^
1 error

The member m disappeared down the mysterious scope hole. The JLS does
address the scope hole in the following specification.
It is a compile-time error if a static class contains a usage of a non-
static member of an enclosing class.25
If you do not already understand why, then you need to read Volume 1 of Java
Rules. As Rose said above, the scope hole is “an unavoidable implication of the
meaning of ‘static,’” which is the subject of Chapter 3 in Volume 1. More gener-
ally, however, you need to fully understand the difference between top-level
classes and inner classes, which is discussed in Chapter 2 of the same volume.
There is no scope hole in inner classes, which are by definition non-static.
As stated in the Inner Classes Specification:

24. Ibid., §9.1.3, “Interface Body and Member Declarations.”


25. Ibid., §8.5.2, “Static Member Type Declarations.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 267


The code of an inner class can use simple names from enclosing
scopes, including both class and instance members of enclosing
classes, and local variables of enclosing blocks. 26

See 3.6 Multiple Current Instances (a.k.a. Levels) in Volume 1 for a closely
related discussion.

2.3.2 Compilation Unit Scope


This term is of my own making, but one I think important to any discussion of
scope. What is compilation unit scope? The term compilation unit scope
applies only to the names of public class and interface types imported using
either a single-type-import or type-import-on-demand declaration. Those type
names are in scope throughout the compilation unit in which the import decla-
ration appears. Note that this applies only to the compilation unit that includes
the import declaration, and not to other compilation units that belong to the
same package.
The term compilation unit scope is necessary to emphasize that import
declarations are only good for the compilation unit in which they appear. The
necessity for inventing this term stems from the fact that novice programmers
sometimes assume that an import declaration typed in one compilation unit
works for all of the other compilation units that belong to the same package. Not
only would that unnecessarily slow the compilation process for many of the
classes and interfaces in a package, but it could potentially introduce naming
conflicts (not to mention the fact that it is technically impossible because a Java
compiler cannot ascertain all of the compilation units that belong to a package).

2.3.3 Members Shadow Declarations in Enclosing Scopes


I do not remember where I first read “inheritance takes precedence over scope,”
but that is how I have come to remember this special rule for nested types. If the
phrase has fallen out of favor it is probably because the inherited members to
which the phrase refers are in scope, which means “inheritance takes prece-

26. Rose, “What are top-level classes and inner classes?”

268 JAVA RULES


dence over scope” could be paraphrased “scope takes precedence over
scope.”
The members of a nested type—either declared or inherited—shadow like-
named entities in an enclosing scope. For example,

class Test {
public static void main(String[] args) {
System.out.println(new Test().new NestedType().s);
}
class ScopeTest { } //declaration is an enclosing scope
class NestedType {
class ScopeTest { } //declared member
String s = new ScopeTest().getClass().getName();
}
}

Executing this program prints Test$NestedType$ScopeTest, which is


the binary name of the inner member class declared in NestedType. There is
nothing remarkable about this example. It can be easily explained using the
“throwing a pebble into a pond” analogy in 2.3 The Fundamentals of Lexical
Scoping. The scope of an inherited member, however, is not so intuitive. For
example:

class Test {
public static void main(String[] args) {
new EnclosingClass.NestedType().print();
}
String scopeTest() { return "inheritance"; }
}
class EnclosingClass {
static String scopeTest() { return "enclosing scope"; }
static class NestedType extends Test {
void print() {
System.out.println(scopeTest());
}
}
}

Executing this program prints inheritance. What makes this example espe-
cially interesting is that the instance method inherited by NestedType is shad-
owing a static method in Test. In a containment hierarchy, an instance

SCOPE, SHADOWING, AND QUALIFIED ACCESS 269


method can shadow a static method, but only in a containment hierarchy. If
you try to doing that in an inheritance hierarchy (which would be hiding, not shad-
owing), the compiler will generate an error message to the effect that an
instance method cannot override a static method.
The specification for shadowing declarations in an enclosing scope is incom-
plete, and has been ever since the original Inner Classes Specification. In the
Second Edition of the JLS it reads as follows.
The scope of a declaration of a member m declared in or inherited by a
class type C is the entire body of C, including any nested type declara-
tions.

If C itself is a nested class, there may be definitions of the same kind


(variable, method, or type) for m in enclosing scopes. (The scopes may
be blocks, classes, or packages.) In all such cases, the member m
declared or inherited in C shadows the other definitions of m.27

This specification was more or less taken directly from the original Inner Class
Specification. It only mentions class types, however. Consequently, it was con-
sequently was only included in Chapter 8, “Classes” of the JLS, but the exact
same specification should be included in comparable sections of Chapter 9,
“Interfaces.” For example,

class Test {
public static void main(String[] args) {
System.out.println(
EnclosingScope.NestedSubInterface.PRECEDENCE_TEST);
}
}

27. Gosling et al., §8.1.5, “Class Body and Member Declarations.” In the Inner Classes Specifica-
tion , the same specification went on to say: “Additionally, unless the [shadowed] definition is a pack-
age member, the simple name m is illegal; the programmer must write C.this.m.” The language
designers were concerned about ambiguity. As stated elsewhere in the Inner Classes Specifica-
tion : “Sometimes the combination of inheritance and lexical scoping can be confusing. For exam-
ple, if the class E inherited a field named array from Enumeration, the field would [shadow] the
parameter of the same name in the enclosing scope. To prevent ambiguity in such cases, Java 1.1
allows inherited names to [shadow] ones defined in enclosing block or class scopes, but prohibits
them from being used without explicit qualification.” This was a contradiction in terms, however. You
cannot say the inherited member shadows the like-named entity in an enclosing scope and in the
same breath require the use of a qualified name. This restriction was lifted shortly after the publica-
tion of the Inner Classes Specification.

270 JAVA RULES


interface EnclosingScope {
String s = "enclosing scope";
interface NestedSuperInterface {
String s = "inheritance";
}
interface NestedSubInterface extends NestedSuperInterface {
String PRECEDENCE_TEST = s;
}
}

Executing this program also prints inheritance.


There is a lessor problem of inconsistency. The following specification is
from 8.3 Field Declarations.
If the class declares a field with a certain name, then the declaration of
that field is said to hide any and all accessible declarations of fields
with the same name in superclasses, and superinterfaces of the class.
The field declaration also shadows declarations of any accessi-
ble fields in enclosing classes or interfaces, and any local vari-
ables, formal method parameters, and exception handler
parameters with the same name in any enclosing blocks.28
[emphasis added]

8.4 Method Declarations has no such specification.

2.3.4 The Scope of Types in the Unnamed Package


The classes and interfaces in an unnamed package are in scope if and only if
there is no package statement at the top of the compilation unit. For exam-
ple, suppose a class such as the following were added to all of the directories on
your user classpath.

public class Directory {


/*
* Do not use the "user.dir" system property to initialize
* this field. It always points to the current working
* directory. Instead, change the string for each
* implementation
*/

28. Gosling et al., §8.3, “Field Declarations.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 271


private static String name = "C:\\Java\\classes");
public String getName() { return name; }
}

As you can see, Directory has no package statement and is therefore a


member of the unnamed package. The following program does not compile
because members of the unnamed package are not in scope.

package com.javarules.examples;
class Test {
public static void main(String[] args) {
String name = new Directory().getName();
System.out.println("directory = " + name);
}
}

Attempting to compile this program generates the following compiler error:

C:\Java\classes\com\javarules\examples>javac Test.java
Test.java:4: cannot access com.javarules.examples.Directory
bad class file:
C:\Java\classes\com\javarules\examples\Directory.class
class file contains wrong class: Directory
Please remove or make sure it appears in the correct subdirectory
of the classpath.
String name = new Directory().getName();
^
1 error

This is how the compiler complains that a class does not have the correct
package statement.
Those Java books that make statements to the effect that “the unnamed
package is automatically imported” or “the unnamed package is always in
scope” are either ignorant of the facts or else they assume that access is from
another member of the unnamed package.
Prior to the 1.4 release, members of the unnamed package could be
imported using a single-type-import declaration. However, the original JLS
included stern warnings against doing so:
Caution must be taken when using unnamed packages. It is possible
for a compilation unit in a named package to import a type from an

272 JAVA RULES


unnamed package, but the compiled version of this compilation unit will
likely then work only when that particular unnamed package is “cur-
rent.” For this reason, it is strongly recommended that compilation
units of named packages never import types from unnamed packages.
It is also recommended that any type declared in an unnamed package
not be declared public, to keep them from accidentally being
imported by a named package.29

These warnings were removed from the Second Edition of the JLS without any
explanation. I can only speculate that this change in the specification was some-
how connected to Bug Id 4361575 in which importing from the unnamed pack-
age was made illegal. The description (copied from the evaluation) of that bug
report begins as follows.
The compiler now correctly scopes import declarations.

Among other effects of this change, the compiler now rejects import
statements that import a type from the unnamed namespace. The com-
piler used to accept such import declarations before, but they were
arguably not allowed by the language (because the type name appear-
ing in the import clause is not in scope). Rather than try to rationalize
the specification with the compiler's behavior, the compiler has been
brought into line with the specification, and the specification is being
clarified to outright say that you can't have a simple name in an import
statement, nor can you import from the unnamed namespace. There
were ample warnings in the language specification warning against
importing names from the unnamed namespace into a named name-
space. Those warnings are no longer necessary, as it is outright illegal.

This is likely to break lots of code, but virtually all of it is example code
rather than production code.

To summarize, the syntax

import SimpleName;

is no longer legal. Nor is the syntax

29. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification (Reading: Addison-
Wesley, 1996), §7.4.2, “Unnamed Packages.” (Do not update.) There is actually a second para-
graph that was removed (a continuation of the warning) which I am not quoting here.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 273


import ClassInUnnamedNamespace.Nested;

which would import a nested class from the unnamed namespace.

To fix such problems in your code, move all of the classes from the
unnamed namespace into a named namespace.30

This analysis is all wrong and for a very simple reason. JLS 6.7 Fully Qualified
Names and Canonical Names states very clearly that
The fully qualified name of a top level class or top level interface
that is declared in an unnamed package is the simple name of
the class or interface.

For every package, top level class, top level interface and primi-
tive type, the canonical name is the same as the fully qualified
name.31 [emphasis added]

Now go back and read the description again. The rationale for this change in the
language is that the name of a class or interface type in the unnamed package is
not in scope, but scope is not an issue. The specification is very clear the canon-
ical names must be used in import declarations and that the simple name of a
class or interface type that is a member of the unnamed package is the canoni-
cal name. This also applies to nested types in the unnamed package because of
the following specification from the same section of the JLS:
A member class or member interface M declared in another class C
has a canonical name if and only if C has a canonical name. In that
case, the canonical name of M consists of the canonical name of C,
followed by ".", followed by the simple name of M.32

I am really at a loss as to why Sun would so totally abandoned its strict policy of
backwards compatibility on this issue.
It is clear from the comments at the bottom of this and related bugs that a
lot of production code was in fact broken. Moreover, language designers usually

30. Description of Bug Id 4361575.


31. Gosling et al., §6.7, “Fully Qualified Names and Canonical Names.”
32. Ibid.

274 JAVA RULES


opt for the greatest flexibility so long as there is no downside. In this case, pro-
grammers have been able to import from the unnamed package for years, so it
is hard to imagine what is the downside. There really is only two explanations for
this decision, either it was a big mistake that got past the review process or else
somebody at Sun just had a hankering to make importing from the unnamed
package illegal before the Java platform reached full maturity. If it is the former,
then this decision has to be undone. If it is the latter, then the specification
requires a number of changes.
The evaluation includes a number of comments that suggest that the JLS is
now very clear on the fact that importing from the unnamed package is illegal.
For example,
OK, this has been integrated and conforms to the latest language spec.
WARNING: users will complain. The compiler apparently allowed import-
ing a type from an unnamed namespace, and the specification arguably
allowed it as well, and now it is clearly illegal in both the spacification
[sic] and the compiler.33

This is blatantly false. Other than removing the warnings against importing
from the unnamed package, the Second Edition of the JLS is totally silent on this
issue. Furthermore, as of this writing (well after this change was implemented)
there is also nothing on the JLS “Maintenance Page” (where you would expect to
find it because this bug was reported after the publication of the Second Edition)
at java.sun.com/docs/books/jls/jls-maintenance.html.
Nevertheless, as late as the 1.4.1_01 release import declarations that
use either a simple name or the fully qualified name of a nested type in the
unnamed package no longer compile. For example,

package com.javarules.examples;
import Directory;
class Test {
public static void main(String[] args) {
String name = new Directory().getName();
System.out.println("directory = " + name);

33. Evaluation of Bug Id 4361575.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 275


}
}

Attempting to compile this program generates the following compiler errors:

Test.java:2: '.' expected


import Directory;
^
Test.java:5: cannot access com.javarules.examples.Directory
bad class file:
C:\Java\classes\com\javarules\examples\Directory.class
class file contains wrong class: Directory
Please remove or make sure it appears in the correct subdirectory
of the classpath.
String name = new Directory().getName();
^
2 errors

A period separator is expected because simple names can no longer be used in


single-type-import declarations.

2.3.5 Circular Dependencies in Type Declarations


That circular dependencies in type declarations compile is really a scope issue
(not unlike the fact that fields can be declared at the bottom of a class). Types
scope to all of the compilation units that belong to the same package. Thus a
type can be used before it is declared (which assumes multiple type declarations
in the same compilation unit). The relevant specification reads:
Types declared in different compilation units can depend on each
other, circularly. A Java compiler must arrange to compile all such
types at the same time. 34

Elsewhere the JLS includes an example of what is meant by this specification:


…declarations of class and interface types need not appear before
uses of the types.

In the example:

34. Gosling et al., §7.3, “Compilation Units.”

276 JAVA RULES


package points;

class Point {
int x, y;
PointList list;
Point next;
}

class PointList {
Point first;
}

the use of PointList in class Point is correct, because the scope


of the class declaration PointList includes both class Point and
class PointList, as well as any other type declarations in other
compilation units of package points.35

In a different context, this is referred to as the automatic compilation of


dependencies.
Circular dependencies compile, but the example in the JLS is potentially mis-
leading because if variable initializers were added the same code would throw a
StackOverflowError at run time. For example,

class Test {
public static void main(String[] args) {
System.out.println(new A().getClass().getName());
}
}
class A {
B b = new B();
}
class B {
A a = new A();
}

The only substantial difference between this example and the one in the JLS is
the addition of instance variable initializers. This example of circular dependen-
cies in type declarations does indeed compile, but attempting to instantiate
either class results in an instance initialization loop (the “hall of mirrors” effect) at

35. Gosling et al., §6.3, “Scope of a Declaration.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 277


run time similar to the one discussed in 1.4.5 StackOverflowError During
Object Initialization. Note that the same StackOverflowError would be
thrown if these classes were declared in different compilation units. In order to
initialize fields that involve circular type dependencies both fields cannot be
instance variables. One or both must be declared static.

2.4 Shadowing
All entities are in scope when they are declared. They are not, however, neces-
sarily visible throughout the lexical construct to which they scope (referred to
simply as their scope). Those lexical constructs are shown in Table 2.1 Lexical
Scoping on page 262. Many entities are shadowed in part of their scope by the
declaration by another entity with the same simple name. This complicates the
rule for when simple names can be used.

The simple name of an entity can be used only if that entity is both in
scope and visible.

The term visible means not shadowed. The design of the Java programming
language (or any lexically scoped programming language for that matter) is that
if two or more entities of the same kind and with the same simple name are in
scope, one is visible and the other is shadowed. The simple name will always
refer to the visible entity. In order to reference the shadowed entity, a qualified
name can be used for static members; the this keyword can be used for
non-static members. Some entities simply cannot be referenced while they
are shadowed. Local variables and parameters shadowed by declarations in a
block class are an example of this.
As with hiding, the visible and shadowed entities are always the same kind of
entity because of the syntactic classification of names according context. More
specifically, both are one of the following.
• Type
• Variable (field, local variable, or parameter)
• Method

278 JAVA RULES


• Label
There really is only one rule for shadowing. I express that rule as follows.

The declaration of an entity shadows like-named entities of the same


kind throughout the scope of the newly declared entity.

This rule applies to type, variable, and method declarations alike (as well as to
labels). The remainder of this section discusses the only three acceptable uses
of shadowing:
• Constructor parameters shadow instance variables
• Types imported using single-type-import declarations shadow members
either declared in other compilation units of the same package or imported
using a type-import-on-demand declaration
• Members of a nested type shadow entities in enclosing classes
These accepted uses of shadowing are listed in the order in which they are most
commonly used.
Java programmers use shadowing all the time in constructors. Constructor
parameters shadow the name of the instance variable to which they are
assigned. This is an important programmer convention that results in much
cleaner and easier to read constructors. For example,

public Locale(String language, String country, String variant) {


this.language = language;
this.country = toUpperCase(country).intern();
this.variant = toUpperCase(variant).intern();
}

This is a simplified constructor from the Locale class. The constructor param-
eters language, country, and variant shadow the like-named fields
throughout the body of the constructor. This particular use of shadowing is
encouraged in the JLS:
…the constructor takes parameters having the same names as the
fields to be initialized. This is simpler than having to invent different

SCOPE, SHADOWING, AND QUALIFIED ACCESS 279


names for the parameters and is not too confusing in this stylized con-
text.36

Be careful of this programmer convention. Should you inadvertently mis-


spell the constructor parameter name, the value of the field is assigned
to itself. No compiler warning is issued, so this can be a difficult problem to
diagnose.
The fact that shadowing is encouraged in constructors is an exception to the
rule, however. The shadowing of fields by local variables or method parameters
is otherwise discouraged. In fact, the last sentence of the same paragraph in the
JLS states: “In general, however, it is considered poor style to have local vari-
ables with the same names as fields.”36 The problem with local variables or
method parameters shadowing fields is that a maintenance programmer
could very easily assign a value to what he thinks is a field when in fact
the field is shadowed and he is actually assigning a value to a local vari-
able or method parameter. This is not a problem in constructors because pro-
grammers are aware of the convention of using like-named constructor
parameters.
Another very common use of shadowing is discussed in 2.2.2 Disambiguat-
ing Type Names. Types imported using a single-type-import declaration are said
to shadow like-named types either declared in other compilation units of the
same package or imported using a type-import-on-demand declaration.
Other than these two very common uses, shadowing is largely confined to
nested types in which declared and inherited members shadow entities in enclos-
ing scopes. In fact, methods can only be shadowed in containment and inner
class hierarchies.37 In an inheritance hierarchy, methods are either hidden or

36. Gosling et al., §14.4.3, “Shadowing of Names by Local Variables.”


37. Basically, the term containment hierarchy refers to a package member and all of the
nested top-level classes declared in that package member. An inner class hierarchy refers
to all of the inner classes (inner member, local, and anonymous classes) in a given top-level
class. Contrary to the JLS, I define top-level class as any class declared static (the same as
John Rose did in the Inner Classes Specification). That means the class at the top of an inner
class hierarchy may be either a package member or a nested top-level class. See 2.12 Containment
and Inner Class Hierarchies in Volume 1 for a complete definition of these terms. Be advised that I
repeat this footnote a number of different times in Volume 2 because these terms are so unusual.

280 JAVA RULES


overridden. See 2.3.3 Members Shadow Declarations in Enclosing Scopes for a
discussion.

2.5 Obscuring
Declarations can be hidden, shadowed, and obscured. As stated in the JLS
“obscuring is distinct from shadowing and hiding.”38 The difference between
them is subtle. You may recall from the last section that both the visible and
shadowed entity (or entities) are one of the following.
• Type
• Variable (field, local variable, or parameter)
• Method
• Label
At the beginning of the next chapter, 3.2 Hiding makes essentially the same
point. Both entities are always one of the following.
• Fields
• Class methods
• Member types
This is precisely what makes obscuring “distinct from shadowing and hiding.” In
obscuring different kinds of entities are involved. Only package and types
names are obscured. There are three possibilities:
• Variable names that obscure package names
• Type names that obscure package names
• Variable names that obscure type names
The last two possibilities are rare because of Java naming conventions, so this
discussion of obscuring focuses largely on variable names that obscure pack-
age names.
Obscuring is based on 6.5.2 Reclassification of Contextually Ambiguous
Names in the JLS. Basically, the syntactic classification of names according to
context is less than perfect. It is very obvious in some contexts that a name

38. Gosling et al., §6.3.2, “Obscured Declarations.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 281


refers to a certain kind of entity. For example, the name in a single-type-import
declaration refers to a type. Contextually ambiguous names, however,
could be qualified type names, field access expressions, or method invo-
cation expressions. For example,

package test; //test is a package


class x {
static final String length = "test is a package name";
static Widget test = new Widget(); //test is a varaible
public static void main(String[] args) {
int[] x = new int[10];
int length = 0;
System.out.println(test.x.length); //What is test?
}
static class test { //test is a type
static String[] x = { "test is a", " type name" };
}
}
class Widget {
String[] x = { "test is a variable name" };
}

In this example, test could be a variable, type, or package name. Because


these are different kinds of entities, it is clear that none of them either
shadow or hide the other. Executing this program initially prints 1, which is the
length of the x array in the Widget class. If the declaration of the Widget
type test variable is deleted, the same program prints 2, which is the length of
the x array in the nested top-level test class. If the declaration of that class is
deleted, the same program prints test is a package name. Here is another
example involving a method invocation expression:

class Test {
public static void main(String[] args) {
String Widget = "Test is a variable name";
System.out.println(Widget.length());
}
}
class Widget {
static int length() {
return 0;

282 JAVA RULES


}
}

Executing this program prints 23, which is the length of the Widget string. In
both examples, the compiler somewhat arbitrarily decides the ambigu-
ous name is a variable, not a type or package. As stated in the JLS:
A simple name may occur in contexts where it may potentially be inter-
preted as the name of a variable, a type or a package. In these situa-
tions, the rules [§6.5.2 Reclassification of Contextually Ambiguous
Names] specify that a variable will be chosen in preference to a type,
and that a type will be chosen in preference to a package.39

In effect, this means the simple name of the test class and test package
cannot be used anywhere in the scope of the test variable. In these examples,
the type name is obscured (and obscures) because the responsible programmer
is flaunting Java naming conventions. That should not happen in practice.
Obscured packages names, however, are a different matter. Java naming
conventions do not help because “names of packages intended only for local
use should have a first identifier that begins with a lowercase letter”40 Thus a
variable name can obscure a package name even when Java naming conven-
tions are followed. For example, nothing can be done to name the shadowed
x.length field in the Widget helper class in the first example because the
test package is doubly obscured. The JLS includes the following two sugges-
tions should the obscuring of package names become a problem.
When package names occur in expressions:

• If a package name is obscured by a field declaration, then


import declarations can usually be used to make available
the type names declared in that package.
• If a package name is obscured by a declaration of a parame-
ter or local variable, then the name of the parameter or local
variable can be changed without affecting other code.41

39. Gosling et al., §6.3.2, “Obscured Declarations.” The actual specification is in §6.5.2, “Reclassi-
fication of Contextually Ambiguous Names.”
40. Ibid., §6.8.1, “Package Names.”
41. Ibid.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 283


The first suggestion is a little cryptic. The implication is that a fully qualified
name is required because some type has not been imported. The suggestion is
to import the type so that the fully qualified name of the type does not have to be
used. Changing from the fully qualified name to the simple name of the type elim-
inates the problem of an obscured package name.
It is specifically because of the possibility of local variables or parameters
obscuring a package name that the JLS includes the following warning:
Local variable or parameter names that consist of only two or three
lowercase letters should not conflict with the initial country codes and
domain names that are the first component of unique package
names.42

The older top-level domain names are the ones you need to be concerned about.
They are com, net, org, gov, mil, and edu. You should probably never
declare local variables or parameters to have one of these names. With the pos-
sible exception of biz, the seven newer ones are not likely to be sources of
widely distributed Java packages. They are biz, info, name, pro, aero,
coop, and museum. By the way, Verisign has an excellent white paper on the
new top-level domain names entitled “Journey to the Right of the Dot: ICANN’s
New Web Extensions.”43

2.6 Observable Compilation Units and Packages


A package named fubar and stored on a floppy disk in Germany or Japan obvi-
ously cannot be used by a developer in the United States of America. The origi-
nal JLS addressed this issue in terms of scope and accessibility:
7.4.3 Scope and Hiding of a Package Name

Which top-level package names are in scope is determined by conven-


tions of the host system.

Package names never hide other names.44

42. Gosling et al., §6.8.6, “Local Variable and Parameter Names.”


43. See www.verisign.com/resources/wp/domain/extensions/
domainExtensions.pdf.
44. Gosling et al., §7.4.3, “Scope and Hiding of a Package Name.” (Do not update.)

284 JAVA RULES


7.4.4 Access to Members of a Package

Whether access to members of a package is allowed is determined by


the host system. The package java should always be accessible, and
its standard subpackages lang, io, and util should always be
accessible.

It is strongly recommended that the protections of a file system or


database used to store Java programs be set to make all compilation
units of a package available whenever any of the compilation units is
available.45

In one of many wonderful usage distinctions Gilad Bracha added to the Second
Edition, packages (and compilation units) are now either observable or not.46
The above specification was changed to read as follows in the Second Edition of
the JLS.
Which compilation units are observable is determined by the host sys-
tem. However, all the compilation units of the package java and its
subpackages lang and io must always be observable. The observ-
ability of a compilation unit influences the observability of its pack-
age.47

7.4.3 Observability of a Package

A package is observable if and only if either:

• A compilation unit containing a declaration of the


package is observable.
• A subpackage of the package is observable.
One can conclude from the rule above and from the requirements on
observable compilation units, that the packages java, java.lang,
and java.io are always observable.48

45. Gosling et al., §7.4.4, “Access to Members of a Package.” (Do not update.)
46. I cannot agree with the analysis in Bug Id 4420532, which is also available on the (unofficial)
Java Spec Report Web site at www.ergnosis.com/java-spec-report/java-language/
jls-7.4.3.html. Recursively defining the observability of packages based on either the observ-
ability of subpackages or the compilation units that belong to the package, and then defining the
observability of subpackages on the observability of compilation units that belong to the subpack-
age, is both intuitive and non-circular.
47. Gosling et al., §7.3, “Compilation Units.”
48. Gosling et al., §7.4.3, “Observability of a Package.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 285


7.4.4 Scope of a Package Declaration

The scope of the declaration of an observable top level package is all


observable compilation units. The declaration of a package that is not
observable is never in scope. Subpackage declarations are never in
scope.

It follows that the package java is always in scope.

Package declaration never shadow other declarations.49

The Second Edition of JLS does not actually define the term observable, but nei-
ther did the original JLS define the “conventions of the host system” except indi-
rectly by the suggestion at the bottom of 7.4.4 Access to Members of a
Package above that file systems and databases allow access to all of the compi-
lation units in a package. It basically means that the development directory, JAR,
or Zip file that contains the package is not on the bootstrap or extension class
path and cannot be loaded using a user-defined class loader (including the appli-
cation or applet class loader). A package on some removal media buried under a
bunch of trash at the bottom of a dumpster in an ally somewhere in downtown
Tokyo, Japan is a good example of an “unobservable” package. A development
directory that cannot be read because of file protections would be an even bet-
ter example.

2.7 Qualified Access


This section is largely an exercise in terminology. The JLS definition of access
suffers from two non-trivial problems. The first problem is that the definition of
access does not take into consideration constructors:
Access is a different concept from scope; access specifies the part of
the program text within which the declared entity can be referred to by
a qualified name, a field access expression, or a method invocation
expression in which the method is not specified by a simple name.50

49. Gosling et al., §7.4.4, “Scope of a Package Declaration.”


50. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java Language Specification,
Second Edition (Boston: Addison-Wesley, 2000), introduction to Chapter 6, “Names.”

286 JAVA RULES


I checked and this is the passage referenced under “term definitions” in the
index of the JLS. In other words, this is the official definition of access in the JLS.
This definition of access is largely indistinquishable from the following definition
of qualified access found elsewhere in the JLS (pay particular attention to the
phrase “means of access”).
Qualified names are a means of access to members of packages and
reference types; related means of access include field access expres-
sions and method invocation expressions. All three are syntactically
similar in that a “.” token appears, preceded by some indication of a
package, type, or expression having a type and followed by an Identi-
fier that names a member of the package or type. These are collec-
tively known as constructs for qualified access.51 [emphasis added]

These three means of access are grouped together not only because they look
the same (or rather are “syntactically similar”), but also because of lexical scop-
ing. An entity that is not in scope must be accessed using a qualified name, field
access expression, or method invocation expression. These are the two halves
of a whole. Where one stops, the other begins.
As shown in Figure 2.2, constructors are a fourth means of access. The
failure to mention constructors in the definition of access in the JLS is an obvi-
ous error of omission because access modifiers are used in their declaration.
The fundamental distinction between constructors (instantiating a class) and
qualified access (accessing the implementation of a class) can be see in the fol-
lowing definition of access control.
Access control applies to qualified access and to the invocation of con-
structors by class instance creation expressions and explicit construc-
tor invocations.52

More precisely, access control applies to each of the following:53

51. Ibid., §6.6, “Access Control.” As with the definition of access in the JLS, this definition of quali-
fied access should specify that it does not include method invocation expressions “in which the
method is not specified by a simple name.”
52. Gosling et al., The Java Language Specification, Second Edition, §6.6, “Access Control.”
53. For the time being at least, I am ignoring the instantiation of nested types. The rationale for
doing so is the nested top-level classes are not substantially different than package members, and
the inner classes are rarely instantiated outside of the class in which they are declared.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 287


Figure 2.2 Two fundamentally different “means of access”

1. Any use of a type name (type privileges)


2. Constructor invocations (instantiation), which includes all of the following.
• Class instance creation expressions
• Explicit constructor invocations using the super keyword
• The newInstance() method in java.lang.Class
3. Field access and method invocation expressions (access to the implementa-
tion of an object)
Always look for the distinction between instantiation and accessing the imple-
mentation of an object when discussing design patterns that feature access con-
trol. They are two fundamentally different means of access.
The remainder of this section discusses the second problem with the defini-
tion of access in the JLS. Access to the members of a package implies the use
of a fully qualified type name. As stated in the JLS: “access specifies the part of
the program text within which the declared entity can be referred to by a quali-
fied name….”50 The problem is that, using this definition of access from
the JLS, most “access” to package members occurs in import declara-
tions, or less frequently in extends or implements clauses, not in
class bodies. Types are imported specifically so that qualified names do not

288 JAVA RULES


have to be used. This results in a situation in which we are looking at simple type

Only fully qualified type names include a package name and thus
“access” the members of a package.

names and thinking access control, which is a mistake. In fact, if a type-import-


on-demand declaration is used to import a public class or interface type,
there may not be a qualified name for that type anywhere in the source code.
The actual access occurs in the compiler when the complex classpath formula is
used to determine the meaning of a simple or partially qualified type name. For
example,

import java.util.*;
class Test {
HashMap map;
}

The JLS definition of qualified access does not specifically cover this
most common means of access. Nevertheless, we know that HashMap is
accessed because it is a member of another package. It is important to dif-
ferentiate between actually “accessing” a package and simply being able
to use the name of a class or interface type. I address this problem by intro-
ducing the term type privileges, which is defined as any use of a type name.
No distinction is made between simple and qualified names.
Type privileges are further divided into class type privileges and interface
type privileges. This is important because interface types cannot be instanti-
ated and all of their members are implicitly public. Therefore interface type
privileges are not relevant to any discussion of access control.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 289


2.8 Access Control
There are three access modifiers:54 public , protected , and private.
The only access modifier that can be used in the declaration of a package mem-
ber is public. If a package member is not declared public, it is said to be
non- public. The members of an interface type are implicitly public. The
members of a class type and constructors can use any of the explicit access
modifiers. Local and anonymous classes are implicitly private to the block in
which they are created (which for anonymous classes created in the variable ini-
tializer of a field is the body of an <init> of <clinit> method) and there-
fore do not use access modifiers.
If none of the explicit access modifiers are used, an entity is said to have
default access. The term package-private is definitely preferred over default
access among the technical writers and software engineers at Sun, so much so
that it ought to be recognized in the JLS. Table 2.2 lists alternative terms for the
access modifiers. Because package-private is so widely used by the software

Table 2.2 Alternative Terms for Access Modifiers


Standard Terminology Other Commonly Used Terms

private accessa class-private


private visibility
containment-hierarchy-private
inner-class-hierarchy-private
block-private

protected access protected visibility

default access (a.k.a. non- public ) package-private


package access
package visibility
implementation visibility

public access public visibility

54. The term access specifier used in a number of very popular Java books is in direct conflict with
the consistent use of access modifier in the JLS. Access modifiers are specified in the declaration
of a class, interface, field, constructor, or method, but there is no such thing as an “access speci-
fier.”

290 JAVA RULES


a. All of the alternative terms for the private access modifier except private visibility are mine. They are
discussed below.

engineers and technical writers at Sun, it enjoys a special status as an alterna-


tive term. I use default access and package-private interchangeably in Java
Rules.
Accessing members of the same package is never an issue because default
access is the minimum level of access for a package member. Likewise, access
control never applies when referencing fields, methods, or nested types that are
members of the same class or interface (including inherited members). Thus the
term access always implies “external access.”55 The definition of external
depends on the scope of the entity accessed. In the case of package members,
for example, “external” means access from another package. In the case of top-
level class or interface types, “external” means access from another top-level
class or interface type. You can see this additional meaning of the term access
in the following definition of accessible.
The Java programming language provides mechanisms for access
control, to prevent the users of a package or class from depending on
unnecessary details of the implementation of that package or class. If
access is permitted, then the accessed entity is said to be accessi-
ble.56
Note that the default is to control access from outside the package, but
not from within the package. The private access modifier is used to con-
trol access from within the same package.
Table 2.3 defines the four levels of access control. The idea that
protected fields and methods are accessible from subclasses declared in
other packages is a gross oversimplification that is repeated in any number of
Java books, especially in tables such as this one. The Java Programming
Language coauthored by no less Dr. Gosling is interesting in this regard. It
includes a section entitled “What protected Really Means” which begins.

55. Gosling et al., The Java Language Specification, Second Edition, Chapter 1, “Introduction.”
56. Ibid., §6.6, “Access Control.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 291


Table 2.3 The Four Levels of Access Control

Accessible From public protected default private


The top-level class or interface type in
which an entity is declared
✔ ✔ ✔ ✔
Other class and interface types in the
same package.
✔ ✔ ✔
See 2.8.1 The protected Access
Modifier.
✔ ✔
Any Java code in which the package is
observable

We noted briefly that making a class member protected means it


can be accessed by classes that extend that class, but that is loose
language.57

I just refuse to go along with the status quo that includes a row labeled “Sub-
classes declared in other packages.” Nevertheless the reader should be aware

A protected instance variable or instance method in fact is not


always accessible from subclasses declared in other packages ,
nor are protected constructors when invoked as the result of
evaluating a class instance creation expression.

that protected is more accessible than default access. Some software engi-
neers and technical writers like to say more public rather than more acces-
sible. The meaning is the same. The fact that protected is more accessible
than default access is significant because of the compiler-enforced method con-
tract. Methods declared protected cannot be overridden by default access
methods. The protected access modifier is discussed in detail in the follow-
ing subsection.

57. Ken Arnold, James Gosling, and David Holmes, The Java Programming Language, Third
Edition , (Reading: Addison-Wesley, 2000), 81.

292 JAVA RULES


Other than to establish the relative accessibility of access modifiers (which
again is important in terms of the compiler-enforce method contract), access
control tables such as Table 2.3 (which can be found in almost every Java book)
are close to useless because both the private and protected access
modifiers have subtle nuances of meaning. Fully explaining the protected
access modifier requires a separate section. The remainder of this section dis-
cusses the private access modifier.
The JLS says only that “if the member or constructor is declared private,
then access is permitted if and only if it occurs within the body of the top level
class that encloses the declaration of the member”58 (emphasis added). It
then defines “top level class” as a package member. This specification is won-
derfully succinct (and a massive improvement over what was in the Inner
Classes Specification), but it is a gross oversimplification for many application
programmers. It reminds me of something Dr. Gosling said in a Bill Venner’s
interview. He quoted Albert Einstein as saying: “Everything should be as simple
as possible, but no simpler.” With all due respect for Gilad Bracha (who I am sure
could hold his own in a conversation with Einstein), I think that saying applies to
the definition of private access in the Second Edition of the JLS. It fails to
mention any of the following.
• There was a very subtle change in the meaning of the private access
modifier when nested types were introduced in the 1.1 release
• Nested types introduced a new level of access in the Java programming lan-
guage that I describe as containment-hierarchy-private
• Inner classes are rarely instantiated outside of the innermost enclosing top-
level class in which they are declared. Thus the accessibility of their
private members outside of that top-level class is a moot point
• Block classes are implicitly private because of their scope. (They are not
inaccessible as was stated in the Inner Classes Specification.)
None of these are earth shattering on their own, but when considered as a whole
they paint an entirely different picture of access control. As shown in Figure 2.3,
everything is either public (or exported if you prefer) or private to some

58. Gosling et al., §6.6.1, “Determining Accessibility.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 293


Figure 2.3 A greatly simplified view of access control a
a. I do not use fixed font for package-private and block-private because in both cases there is no access mod-
ifier used. This figure does not take into consideration the protected access modifier, which is more about
inheritance than it is access control.

lexical construct (if you are willing to accept my definition of containment and
inner class hierarchies as lexical constructs,59 comparable to packages).
The main thing that is being glossed over in the Second Edition of the JLS is
a very subtle change in the meaning of the private access modifier.

Prior to the introduction of nested types in the 1.1 release, a


private member was always in scope. Now a private member
can be either in scope or accessible using a qualified name,
field access expression, or method invocation expression.

59. Basically, the term containment hierarchy refers to a package member and all of the
nested top-level classes declared in that package member. An inner class hierarchy refers
to all of the inner classes (inner member, local, and anonymous classes) in a given top-level
class. Contrary to the JLS, I define top-level class as any class declared static (the same as
John Rose did in the Inner Classes Specification). That means the class at the top of an inner
class hierarchy may be either a package member or a nested top-level class. See 2.12 Containment
and Inner Class Hierarchies in Volume 1 for a complete definition of these terms. Be advised that I
repeat this footnote a number of different times in Volume 2 because these terms are so unusual.

294 JAVA RULES


These are the two halves of a whole. Where one stops, the other begins. For
example,

class Test {
private String s = "not in scope but accessible";
static class NestedType {
void print() {
System.out.println(s); //COMPILER ERROR
System.out.println(new Test().s);
}
}
}

In this example, s is not in scope (because of the mysterious scope hole), but it
is accessible using a field access expression in which the primary expression is
a class instance creation expression. This is significantly different from
being able to use the simple name of a private field or method.
My approach to this subject depends heavily on the definition of containment
and inner class hierarchies in Volume 1. I begin by restating the specification for
the accessibility of members and constructors in nested types:

There is no access control in a containment or inner class


hierarchy. Therefore, every entity is either in scope or accessible.

Every field, method, constructor, or member type declared in a containment or


inner class hierarchy (or package member, if you prefer) is either in scope or
accessible from anywhere else in the same containment or inner class
hierarchy. It simply does not matter if the entity is declared private; it does
not matter how deeply types are nested; and it does matter if the entity is
static or non-static. Why is every entity either in scope or accessible? As
stated in the JLS, access control is designed to “prevent the users of a package
or class from depending on unnecessary details of the implementation of that
package or class.”60 Within a package member (or what the JLS calls a top-
level class), there is no user.

60. Ibid., §6.6, “Access Control.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 295


There are two rules that determine when a member is either in scope or
accessible in containment or inner class hierarchies:
• The simple name of a member cannot be used outside of the type in which it
is declared (with the notable exception of importing nested types).
• The non-static members of a class (Inner member classes, instance vari-
ables, and instance methods) cannot exist apart from an instance of the
class in which they are declared
In the common case of a nested type accessing the private members of an
enclosing class, these rules are not very useful. In fact, they have to be
explained away. For example,

class Test {
private static String s = "in scope";
class InnerMemberClass {
void print() {
System.out.println(s);
}
}
static class NestedTopLevelClass {
void print() {
System.out.println(s);
}
}
}

The Test class encloses both InnerMemberClass and NestedTop-


LevelClass . This is the same as saying that the nested types are declared
inside of the Test class. Thus the simple name of s can be used. The static
members of an enclosing class are always in scope. The non-static
members of an enclosing class are either in scope or accessible. They are in
scope in most inner classes (orphaned local and anonymous classes being the
only exception) because of the link variable. In nested top-level classes, nested
interfaces, and orphaned local and anonymous classes, they are accessible. Now
look what happens if the static modifier is removed from the declaration of s:

class Test {
private String s = "in scope";

296 JAVA RULES


class InnerMemberClass {
void print() {
System.out.println(s);
}
}
static class NestedTopLevelClass {
void print() {
System.out.println(new Test().s);
}
}
}

The s field is now an instance variable. Therefore the second rule comes into
play. In the InnerMemberClass, the simple name of s can still be used
because it is implicitly qualified by Test.this. See 1.10.1 The Meaning of a
Simple Field or Method Name for a discussion. There is no enclosing instance of
the Test class in NestedTopLevelClass, however. Thus the second rule
applies and an instance of the Test class must be created before the s
instance variable can be accessed. See 2.3.1 The Mysterious Scope Hole for a
discussion.
These rules are most useful when one nested type accesses the members
of another nested type. For example,

class Test {
class InnerMemberClass {
private String s = "accessible";
void print() {
System.out.println(new NestedTopLevelClass().s);
}
}
static class NestedTopLevelClass {
private String s = "accessible";
void print() {
InnerMemberClass imc = new Test().new InnerMemberClass();
System.out.println(imc.s);
}
}
}

SCOPE, SHADOWING, AND QUALIFIED ACCESS 297


These classes are not declared inside of each other. Their private members
are therefore not in scope. They are, however, accessible. The s field is an
instance variable, so the second rule also comes into play.
This is where things get interesting because InnerMemberClass also
cannot exist apart from an instance of the class in which it is declared. There is
no enclosing instance of Test in NestedTopLevelClass, so in order to
access s an instance of both Test and InnerMemberClass must be cre-
ated. Here it is important to notice that the type of imc is InnerMember-
Class , not Test.InnerMemberClass. Something very counterintui-
tive is happening here. Although an instance of both Test and Inner-
MemberClass must be created in order to access the s instance variable, typ-
ing Test.InnerMemberClass as the type name is optional. Why is that?
Because InnerMemberClass is in scope anywhere in the Test class. This
is a consequence of 2.2.2 Disambiguating Type Names. The compiler simply
knows that the meaning of InnerMemberClass is Type.InnerMember-
Class , so there is no need to type the longer type name. The same would
be true if Test and InnerMemberClass were public and the latter were
imported into a different compilation unit.
When nested types were added to the Java programming language, the
meaning of the private access modifier substantially changed. I therefore
use the following four alternative terms when discussing private access:
• containment-hierarchy-private
• inner-class-hierarchy-private
• class-private
• block-private
These alternative terms are coordinate with package-private. The question
they answer is, “private to what?” They are only useful, however, if you
think of package members as containment hierarchies, inner class hierarchies,
or top-level classes in which no nested types are declared. In Volume 1 of Java
Rules, I argue that containment and inner class hierarchies are lexical con-
structs not unlike packages in that they are comprised of multiple types. By
adopting these terms (along with package-private), it is possible to say that all

298 JAVA RULES


entities are either public or private to some lexical construct. Thus class-
private means that no other class can access the field or method. This is
the old meaning of private (before nested type were added to the language)
in which the member is always in scope. It implies a field or method declared in
a package member in which there are no nested types. I realize, however, that
not all readers will appreciate this usage distinction.
It is interesting to note that containment-hierarchy-private is a new
level of access that did not exist prior to the 1.1 release. It is somewhere in
between default access and class-private. John Rose alluded to this in the
Inner Classes Specification when he said:
The ability to nest classes in this way allows any top-level class to pro-
vide a package-like organization for a logically related group of second-
ary top-level classes, all of which share full access to private
members.61

Within that “logically related group of secondary top-level classes”61 there is no


access control, which is what Rose means when he says “all of which share full
access to private members.”61 Equally as important is the fact that some or all
of the nested top-level classes and interfaces can be declared private, mak-
ing them inaccessible outside of the containment hierarchy (even from other
members of the same package).
The term inner-class-hierarchy-private is imprecise. If the class at the
top of an inner class hierarchy is a nested top-level class, then all of the
private members in the inner class hierarchy are indeed accessible from any-
where in the containment hierarchy (or the package member in which they are
declared). This explains why inner-class-hierarchy-private and containment-
hierarchy-private are shown in the same box in Figure 2.3, “A greatly simpli-
fied view of access control” on page 294. The reason why I discount this impre-
cision and use the term inner-class-hierarchy-private is that inner member
classes are rarely if ever instantiated outside of the top-level class in which they
are declared.

61. John Rose, “What are top-level classes and inner classes?”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 299


The syntax for declaring local and anonymous classes does not include the
use of access modifiers. The Inner Classes Specification refers to them as
inaccessible:
…a class defined by a block…is not a member of its enclosing class,
and so it cannot be named outside of its block. This is the same scop-
ing restriction as applies to local variables, which also cannot be
named outside of their blocks. In fact, any class contained in a block
(whether directly or inside an intervening local class) cannot be named
outside the block. All such classes are called inaccessible.62

The Second Edition of the JLS does not use the term inaccessible, saying only
that
It is a compile-time error if a local class declaration contains any one of
the following access modifiers: public, protected, private, or
static.63
It is a mistake to characterize local and anonymous classes as inaccessible. The
fact is that other local and anonymous classes in the same block can access the
members of a local class, including members declared private. For example,

class Test {
public static void main(String[] args) {
class A {
private String s = "accessible";
}
class B {
private void print() {
System.out.println(new A().s);
}
}
new B().print();
}
}

Executing this program prints accessible. This is actually a completely dif-


ferent level of access that I refer to as block-private.

62. Rose, “How do inner classes work?”


63. Gosling et al., §14.3, “Local Class Declaration.”

300 JAVA RULES


I would like to close this section with a word of praise for the javac team at
Sun. Access control is “a static property that can be determined at compile
time.”64 If access is not permitted, a compiler error is generated. These error
messages have become very precise over the past few years. For example,

public class Test {


public static void main(String[] args) {
Widget widget = new Widget();
}
}
class Widget {
private Widget() {};
}

Executing this program generates the following compiler error:

Test.java:3: Widget() has private access in Widget


Widget widget = new Widget();
^
1 error

Access control error messages were not always so precise. This same program
used to generate the following compiler error:

Test.java:3: No constructor matching Widget() found in class


Widget
Widget widget = new Widget();
^
1 error

The No constructor matching and No method matching error mes-


sages were very confusing for students.

2.8.1 The protected Access Modifier


If ever something needed to be demystified it is the protected access modi-
fier. Most Java books on the market today completely ignore the intricacies of
protected access. The JLS approach to the subject is sound, but requires
explanation:

64. Gosling et al., §6.6, “Access Control.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 301


A protected member or constructor of an object may be accessed
from outside the package in which it is declared only by code that is
responsible for the implementation of that object.65

The conceptual difficulty arises from the fact that the meaning of the
protected access modifier depends on the entity modified. It is very much
like the static modifier in this regard. The protected access modifier has
a different meaning for each of the following kinds of entities:
• static fields and methods, and all member types (including inner mem-
ber classes)
• non-static fields and methods
• Constructors
The grouping in the first bulleted item in and of itself presents a conceptual diffi-
culty because inner classes are not normally grouped together with static
members.
Most programmers think protected members are accessible from sub-
classes declared in different packages. This is true, but for non-static fields
and methods as well as for constructors how the member is accessed
must be taken into consideration. Here is an example of what most program-
mers think of as protected access:

package com.javarules.examples;
public class Superclass {
protected static void print() {
System.out.println("Hello World!");
}
}

The following program that accesses the protected print() method is a


member of the unnamed package.

import com.javarules.examples.*;
class Test extends Superclass {
public static void main(String[] args) {
Superclass superclass = new Superclass();
superclass.print();

65. Gosling et al., §6.6.2, “Details on protected Access.”

302 JAVA RULES


}
}

Executing this program prints Hello World!. The print() method is


accessible from a subclass declared in a different package. If the static
modifier is removed from the declaration of this method, however, the exact
same program generates the following compiler error.

Test.java:5: print() has protected access in


com.javarules.examples.Superclass
superclass.print();
^
1 error

The non-static print() method is inaccessible in this example. The


first thing that must be done, therefore, to demystify the protected access
modifier is to define it in terms of both static and non-static fields and
methods. Here I am going to introduce some new terminology in an effort to dis-
pel the mistaken notion that there is a single meaning for the protected
access modifier. The basic rule of protected access is stated as follows.

Outside of the package in which it is declared, protected mem-


bers can only be accessed from within the body of a subclass.

This much is generally understood. Nothing more need be said to explain


protected access for either static fields and methods or nested types.
Only the basic rule applies. To define all protected access in terms of the
basic rule, however, is a gross oversimplification. There are additional, highly-
specialized rules for instance variables, instance methods, and constructors.
The remainder of this section discusses those rules. Before doing so, however,
it is interesting to note that protected inner member classes are the only
non-static entities for which only the basic rule applies. For example,

package com.javarules.examples;
public class Superclass {
protected class InnerMemberClass {
public InnerMemberClass() { }
}

SCOPE, SHADOWING, AND QUALIFIED ACCESS 303


protected static class NestedTopLevelClass {
public NestedTopLevelClass() { }
}
}

The following subclass is a member of the unnamed package.

import com.javarules.examples.Superclass;
import com.javarules.examples.Superclass.*;
class Test extends Superclass {
public static void main(String[] args) {
InnerMemberClass imc = new Superclass().
new InnerMemberClass();
NestedTopLevelClass ntlc =
new Superclass.NestedTopLevelClass();
}
}

As this example shows, subclasses declared in other packages have type privi-
leges for all of the protected member types declared in a superclass, even if
the protected member type is an inner member class.
The fact that static members and member types declared protected
can be accessed by subclasses declared in other packages irrespective of the
class of the object accessed was actually a bug in the Java programming lan-
guage. It came to the attention of the language designers as a result of Bug Id
4033907. That in turn led to further relaxation of the rules concerning access to
protected static members from subclasses declared in other packages.
Those changes were introduced in the 1.3 release and were subsequently for-
malized in the Second Edition of the JLS. This Bug Id includes comments from
Guy Steele, Gilad Bracha, and other important software engineers at Sun. It is an
historically important bug, —one that I hope is never removed from the Bug
Database. My reason for mentioning this change to the Java programming lan-
guage is that it shows all the more that the basic rule is inadequate to fully
explain protected access.
The rule for non-static fields and methods is by far the most difficult to
understand. Looking only at the examples involving the protected print()
method in Superclass, you might conclude that subclasses declared in other

304 JAVA RULES


packages cannot access protected instance methods declared in a super-
class, but you would be wrong. For example,

import com.javarules.examples.*;
class Test extends Superclass {
public static void main(String[] args) {
Test test = new Test();
test.print();
}
}

This program compiles (using either a static or non-static print()


method in Superclass). Other than instantiating the subclass instead of the
superclass, nothing else has changed. So why is the protected print()
method now accessible? First I will examine the JLS answer to this question
before formulating my own.
A protected member or constructor is always accessible from within the
package in which it is declared because protected access is more accessi-
ble (or more public) than default access. The rules for accessing a
protected member outside of the package in which it is declared are given in
the JLS as follows.
6.6.2.1 Access to a protected Member

Let C be the class in which a protected member m is declared.


Access is permitted only within the body of a subclass S of C. In addi-
tion, if Id denotes an instance field or instance method, then:

• If the access is by a qualified name Q.Id, where Q is an


ExpressionName, then the access is permitted if and only if
the type of the expression Q is S or a subclass of S.
• If the access is by a field access expression E.Id, where E
is a Primary expression, or by a method invocation expres-
sion E.Id(…), where E is a Primary expression, then the
access is permitted if and only if the type of E is S or a sub-
class of S.66
This is the entire section, and defines what the JLS means by “code that is
responsible for the implementation of an object.”65 This rather odd language is
directed primarily at the bulleted items, which only apply to instance variables

SCOPE, SHADOWING, AND QUALIFIED ACCESS 305


and instance methods. The “code that is responsible for the implementation of
an object” is defined as follows:
• The code that is responsible for the implementation of an object is the class
type in which the object is defined as well as any superclasses of that class
type. This much is obvious
• A superclass is only responsible for the implementation of subclasses to the
extent that protected instance variables or instance methods declared in
the superclass are inherited by subclasses. It is not responsible for
protected instance variables and instance methods declared in sub-
classes, and therefore cannot access them. One need only look as far as
the Strategy design pattern to understand the necessity of a superclass to
access protected members inherited by a subclass. The classic exam-
ple of this design pattern is Dr. Gosling’s animated sort algorithm applet
familiar to so many Java programmers. There is a detail analysis of the
code that implements this applet in 2.8.3 Members More Accessible Than
Their Class Type.
• A subclass is not responsible for the implementation of a superclass object,
and therefore cannot access the protected instance variables and
instance methods of a superclass object
The JLS defines this in terms of the primary expression general form. The notion
that protected members are accessible from within the body of subclasses
declared in different packages is further constrained by the type of the primary
expression. Look again at this example from above:

package com.javarules.examples;
public class Superclass {
protected void print() {
System.out.println("Hello World!");
}
}

66. Gosling et al., §6.6.2.1, “Access to a protected Member.” At the beginning of Table 2.2.1
the difference between a qualified name and a field access or method invocation expression is dis-
cussed. Based on that discussion, I am at a loss to explain the first bulleted item in this specifica-
tion. Access to an instance variable is never by means of a qualified name. This specification is
just plain wrong to suggest otherwise. I am tempted to address this in the main body of the text, but
to do so would only serve to distract someone trying to understand the protected access modi-
fier. Later on when I say “the JLS defines this in terms of the primary expression general form” I am
deliberately ignoring the first bulleted item.

306 JAVA RULES


The following program does not compile because, although access occurs from
within the body of a subclass, superclass is not an instance of the subclass.
It is an instance of the superclass in which the protected member is
declared.

import com.javarules.examples.*;
class Test extends Superclass {
public static void main(String[] args) {
Superclass superclass = new Superclass();
superclass.print();
}
}

In the example that did compile, test was an instance of the subclass. Why is
the print() method inaccessible using superclass as a reference? This is
the same as asking: What is being protected?

The answer is that instances of the superclass are being pro-


tected from subclasses declared in other packages.

The superclass itself does not need protecting (because protected members
are in fact accessible from subclasses declared in other packages), but
instances of the superclass do. Those subclasses cannot change the value of a
protected instance variable in a superclass object. Nor can they invoke a
protected instance method (as in this example) if the target object is an
instance of the superclass in which the protected instance method is
declared. This should shed some light on what the JLS means by “code that is
responsible for the implementation of an object.”65
An entirely different approach to this subject is possible. It requires first
exploring the idea that protected instance variables and instance meth-
ods (and protected constructors for that matter) are at once both
accessible and inaccessible. Access control is normally discussed strictly in
terms of the member accessed: public members that are not in scope are
always accessible (assuming the package in which they are declared is observ-
able); a default access member is accessible from class or interface types that

SCOPE, SHADOWING, AND QUALIFIED ACCESS 307


are members of the same package; and private members are inaccessible.
In all three cases, a member is either accessible or inaccessible. The
protected access modifier requires that you break with this thinking because
a protected member may be at once both accessible and inaccessible.
There is a very precise reason for this.67

Hidden instance variables and overridden instance methods


accessed using the super keyword are considered members of
the direct superclass.

For example,

package com.javarules.examples;
public class Superclass {
protected void print() {
System.out.println("Hello World!");
}
}

The protected print() method is overridden in the following subclass. It is


therefore a member of the superclass and is both accessible and inaccessible in
the following subclass.

import com.javarules.examples.Superclass;
public class Test extends Superclass {
public static void main(String[] args) {
new Test().test();

67. Indeed, there is a section in the JLS entitled “Accessing Superclass Members using super”
which includes examples of accessing hidden instance variables. This is not easily understood
because whenever the super.fieldName or super.methodName general forms are used,
this is implicitly used as the target reference. How can this be used to access hidden instance
variables or invoke overridden instance methods that exist only in the superclass method dispatch
table? The answer is very different for fields versus methods. Hidden fields are not inherited by sub-
classes, but they are nevertheless included in the “list of instance variables” that comprise an
instance of the subclass in memory. See 4.11 The Object Class in Volume 1 for a discussion of
how objects are implemented. In the case of the super.methodName general form, the answer
to this question requires an understanding of invocation modes and is discussed in 1.11.3 Overrid-
ing and Dynamic Method Lookup. See also 3.4.1.1 this is Polymorphic, super is Not in Volume 1
for a discussion of how the super keyword is implemented. Based on this explanation of
protected instance variables and instance methods being at once both accessible and inacces-
sible, the analysis in Bug Id 4493343 (and not the JLS) is flawed.

308 JAVA RULES


}
void test() {
this.print();
super.print(); //SUPERCLASS MEMBER IS ACCESSIBLE
Superclass superclass = new Superclass();
superclass.print(); //SUPERCLASS MEMBER IS INACCESSIBLE
}
protected void print() {
System.out.println("Goodbye Cruel C++ World!");
}
}

This program does not compile because of the second print() method invo-
cation. The only difference is that this is implicitly used as the target reference
in the first method invocation whereas an instance of the superclass is used in
the second. Thus a different mindset is required. You must think in terms of
the class of the object referenced, not the member accessed.
While this analysis of protected instance variables and instance methods
being at once both accessible and inaccessible is technically correct, it suffers
from the same problem as the “code that is responsible for the implementation
of an object”65 explanation of protected access in the JLS in that it is too
complicated. This leads to an unfortunate disconnect between how this subject
is presented by technical writers and what most programmers actually think.
Truth be told, application programmers more or less think of protected
instance variables and instance methods as having default access. In that sense,
the primary use of the protected access modifier is not access control.

The primary use of the protected access modifier is to allow sub-


classes to inherit instance variables and instance methods. The same
instance variables and instance methods are generally thought of as
being inaccessible outside of the package in which they are declared.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 309


This explains why the protected access modifier is usually discussed in the
context of “designing a class for inheritance,”68 “designing a class to be
extended,”69 or in my case in 3.9 Designing Extensible Classes.
What about protected constructors? They are not inherited because they
are not members of a class. They are, however, explicitly invoked in subclass
constructors using the super keyword. A protected constructor can be
explicitly invoked in the body of subclass constructors declared in different pack-
ages. They cannot, however, be invoked as a result of evaluating a class
instance creation expression. For example,

package com.javarules.examples;
public class Superclass {
protected Superclass(String s) {
System.out.println(s);
}
}

The protected Superclass(String s) constructor is both explicitly


invoked in the following Test(String s) subclass constructor as well as
being invoked as the result of evaluating a class instance creation expression.

package dummy;
import com.javarules.examples.Superclass;
public class Test extends Superclass {
Test(String s) {
super(s);
}
public static void main(String[] args) {
new Test("protected constructor"); //OKAY
new Superclass("protected constructor"); //COMPILER ERROR
}
}

68. Joshua Bloch, Effective Java Programming Language Guide, (Boston: Addison-Wesley,
2001), “Item 15: Design and document for inheritance or else prohibit it.”
69. Ken Arnold and James Gosling, The Java Programming Language, Second Edition (Read-
ing: Addison-Wesley, 1998), §3.11, “Designing a Class to Be Extended.”

310 JAVA RULES


This program does not compile because the subclass attempts to instantiate the
superclass using a protected constructor. Accessing protected con-
structors is discussed further the next section.
There is what appears at first glance to be an exception to this rule for
protected constructors. Anonymous class instance creation expressions not
only can invoke a protected constructor, but can do so in any class whatso-
ever. For example,

package dummy;
import com.javarules.examples.Superclass;
public class Test {
public static void main(String[] args) {
new Superclass("protected constuctor") { };
}
}

Notice that Test no longer extends Superclass, and these classes are now
in different packages. The Test program compiles and when executed prints
protected constructor. How is this possible you ask? Well, actually the
protected subclass constructor is being invoked from within the body of the
anonymous subclass. In that sense, this is not really an exception to the rule at
all. It is just a subclass that has neither a package statement nor a class header
to make it obvious that the access occurs from within the body of a subclass.
This section has defined the meaning of the protected access modifier
in great detail. 3.9 Designing Extensible Classes discusses how protected
fields, methods, constructors, and member types are actually used. If you want
to fully develop your understanding of protected access, you should read
that section now.

NOTE 2.1
The following section was moved from Volume 1 and corrected as per
the following comment at the bottom of Bug Id 4116802, appropriately
named “Can a nested class access protected fields inherited by an en-
closing class?”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 311


As per John Rose and Guy Steele, this is a compiler bug, not a
spec bug -- such fields should be accessible in inner classes. --
- The spec has been officially clarified to permit access to
inherited members of an enclosing class. The new compiler
follows the amendment [sic] spec.
Somehow I missed this comment (the text runs on for many pages) and
the relevant spec in the Second Edition of the JLS (which indeed does
not make an exception for protected members that are inherited).
Worst of all, I failed to test this myself. My only saving grace is that
protected members inherited by enclosing classes were not acces-
sible from within the body of a nested type prior to the 1.3 release, and
Bug Id 4116802 to this very day remains marked “Closed, will
not be fixed” (with “Released Fixed” blank) when in fact it led to
a very significant change in the Java programming language.
This is confusing to say the least. Nevertheless, as a technical writer I
had an obligation to test the statements made in that bug report
against the latest release and failed to do so. (Lots of mistakes were
made in Volume 1. It was my first attempt at technical writing.) I apol-
ogize for any confusion. Note also that I made important corrections to
the “hypothetical” source code transformations.

2.8.2 Full Access to the Members of an Enclosing Class


Inner classes have full access to the members of an enclosing class, including
both static and non-static members. Here is an example of accessing a
private instance variable in an enclosing class:

class Test {
private String s = "this is a private instance variable";
public static void main(String[] args) {
new Test().new InnerMemberClass().print();
}
class InnerMemberClass {
void print() {System.out.println(s);}
}
}

312 JAVA RULES


Executing this program prints this is a private instance variable.
There is a lot more going on here than meets the eye. The compiler generates a
separate class file for each of these classes. Most Java programmers are aware
of this because the class files, which include one or more $ symbol in their
binary (or bytecode) names, are very “in your face” (or rather on your hard-
drive). What you may not know about inner classes is that they are all trans-
formed into top-level, package members by the compiler.

All inner classes, including even anonymous classes, are package


members after compilation.

The significance of transforming nested types into package members is that


source code transformations are required to make the private members of
what used to be a lexically enclosing class accessible from within the body of a
transformed inner class. (Default and protected access is not an issue
because the transformed classes are members of the same package.) This is
just a matter of how nested types were implemented in the 1.1 release. No
changes were permitted to the JVM because doing so would have overwhelmed
the fledging Java developer community and threatened the very survival of the
Java platform. Nested types were implemented entirely in the compiler.
The intent of these source code transformations is to defeat the normal
access control mechanism in a JVM. They are referred to as “hypothetical” in the
original Inner Classes Specification because in actuality there is no source
code that corresponds to the class files a compiler generates for nested types.
For example, the “transformed” source code for Test and
InnerMemberClass looks like this:

class Test {
private String s = "this is a private instance variable";
public static void main(String[] args) {
new Test().new Test$InnerMemberClass().print();
}
String access$000() { //COMPILER-GENERATED ACCESSOR METHOD
return s; //DEFEATS NORMAL ACCESS CONTROL
}

SCOPE, SHADOWING, AND QUALIFIED ACCESS 313


}
class Test$InnerMemberClass {
private Test this$0; // COMPILER-GENERATED LINK VARIABLE
// JVM QUIETLY PASSES BOTH this AND this$0
Test$InnerMemberClass (Test this$0) {
this.this$0 = this$0;
}
void print() {System.out.println(this$0.access$000());}
}

The inner class is said to be “permanently associated with its enclosing


instances.”70 Any name that begins with this$ is a reserved word so that link
variables cannot be changed from within the inner member class.
The same this mechanism discussed in 3.4 The this and super Key-
words in Volume 1 that passes a reference to the target object used to initialize
this also passes a reference to the qualifying instance used to initialize the
this$0 link variable. The only difference is that this is passed to native code
and this$0 is passed to a constructor automatically generated by the com-
piler. (The default constructor for inner classes is never a no-argument construc-
tor.)
In this example, the value of the this$0 constructor parameter corre-
sponds to the instance of Test created by new Test() in the following line of
code from the main method.

new Test().new Test$InnerMemberClass().print();

The link variable is to an inner class hierarchy what the extends clause is to
an inheritance hierarchy. They literally “link” an inner class to an instance of the
innermost enclosing class. That link is used to invoke compiler-generated
accessor and mutator methods that have access to the private mem-
bers of the enclosing class (thus defeating the normal access control
mechanism in a JVM). Similar transformations are required for nested top-
level classes, nested interfaces, and orphaned local and anonymous classes
that access the private static members of an enclosing type, only there is
no link variable.

70. Rose, Inner Classes Specification, “How do inner classes affect the idea of this in Java code?”

314 JAVA RULES


2.8.3 Members More Accessible Than Their Class Type
This section takes a very close look at the following specification from the JLS:
A member (class, interface, field, or method) of a reference (class,
interface, or array) type or a constructor of a class type is accessible
only if the type is accessible and the member or constructor is
declared to permit access.71 [emphasis added]

This specification influences Figure 2.4, which is repeated from above. In this

Figure 2.4 Two fundamentally different “means of access”

high-level view of access control, class type privileges appear to control both
instantiation and access to the implementation of an object, but is this
really true? The analogy I like to use are safety deposit boxes in a bank. Before
you can open a safety deposit box you have to get through the door of the bank
vault. Is the access modifier of a class type like the door on the bank vault? The

71. Gosling et al., §6.6.1, “Determining Accessibility.”

SCOPE, SHADOWING, AND QUALIFIED ACCESS 315


answer is No and Yes. This is really only true of constructors. The accessibility

Regardless of their access modifier, constructors are no more


accessible than their class type.

of constructors is inextricably linked to their class type because both entities


have the same name. For example, in the class instance creation expression
new com.acme.Widget(), Widget is both a class type and a constructor
name. Both must be accessible. This is precisely why default constructors have
the same level of access as their class type. (Normally, the class type is
accessed before the constructor because the fully qualified name of the class
type appears in an import declaration, but this changes nothing.)
It generally makes no sense to declare a constructor more accessible than
the class type in which it is declared. However, there is an exception to this rule.
The default constructor for a protected class is also protected, which
means subclasses can access the protected type, but cannot invoke the
default constructor in a class instance creation expression. To instantiate the
protected superclass from within the body of a subclass declared in a differ-
ent package, a public constructor is required. For example,

package com.javarules.examples;
public class Superclass {
protected class NestedSuperClass {
//uses default constructor
}
}

The following subclass is a member of the unnamed package.

import com.javarules.examples.Superclass;
public class Test extends Superclass {
public static void main(String[] args) {
Superclass superclass = new Superclass();
NestedSuperClass nested = superclass.new
NestedSuperClass();
}
}

316 JAVA RULES


Attempting to compile this program generates the following compiler errors:

Test.java:5: NestedSuperClass() has protected access in


com.javarules.examples.Superclass.NestedSuperClass
NestedSuperClass nested = superclass.new
NestedSuperClass();
^
1 error

The significance of this is that by default subclass programmers cannot


instantiate a protected type. This is consistent with the fact that they also
cannot access the protected implementation of a superclass object. Here it
helps to remember that instances of the superclass are being protected
from subclasses declared in other packages. Instantiation and implementa-
tion, however, are two fundamentally different means of access. Declaring a
public constructor allows for instantiation while still protecting the implemen-
tation.
The other way around this problem is to extend the protected type. The
default constructor for a subclass declared in another package can access the
protected superclass constructor using the super keyword either explicitly
or implicitly, as in the following example.

import com.javarules.examples.Superclass;
public class Test extends Superclass {
class NestedSubClass extends NestedSuperClass { }
public static void main(String[] args) {
NestedSubClass nested = new Test().new NestedSubClass();
}
}

This program does compile.


It is obvious that the accessibility of class types control instantiation
because you must name the class type in a class instance creation expression.
Once the cat is out of the bag so-to-speak (the class type is accessible), how-
ever, the access modifiers of constructors must be used in order to control
instantiation. Utility classes and the Singleton design pattern are examples of
using inaccessible constructors to control instantiation.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 317


Unlike constructors, the members of a class (fields, methods, and member
types) can be more accessible than their class type. Most objects have more
than one type that can be used to access their implementation (objects whose
class type is Object being the only exception). A member that is inaccessible
in one type may be accessible in another. There are three cases to consider:
• Abstract superclasses that are extended, but never instantiated: In the
core API, the name of these classes often begins with Abstract (for
example, AbstractList in the java.util package). Such classes
may be either public or non-public, but in either case have an entirely
different motivation than the implementation-only classes discussed in the
next bulleted item. The methods in such a class pretty much cover the full
spectrum of access control. They range from private to protected to
default implementations of public methods and may even be declared
public final. The public methods in such a class are accessible out-
side of the package in which they are declared only as inherited members of
an accessible subclass.
• Implementation-Only Classes: This term is of my own making. Implemen-
tation-only classes are non-public classes (very often private inner
member classes, local classes, or anonymous classes) a reference to which
is returned by a public method. The public method is very often a fac-
tory method such as iterator() in the Collections Framework. Any
public method can return a reference to an instance of a non-public
class declared in the same package. The implication of doing so is that
the reference is assigned to a superclass or superinterface type variable.
The only public methods in an implementation-only class are those that
override public methods in the superclass or superinterface. Those
members are indeed more accessible than their class type. Implementation-
only classes differ from other package-private or “helper” classes in that
instances of the class are used outside of the package in which they are
declared. This distinction is of fundamental importance to understanding
object-oriented design.
• The Compiler-Enforced Method Contract: In cases such as the
toString() method, subclasses have no choice but to declare members
more accessible than their class type because of the compiler-enforced
method contract. This is true even for package-private or “helper” classes

318 JAVA RULES


that simply are not used outside of the package in which they are declared.
The access modifier for such methods are misleading in that sense.
The Strategy design pattern comes to mind in this context (as an example of
both the first and second bulleted items). The classic example of this design pat-
tern is Dr. Gosling’s animated sort algorithm (otherwise known as the “Sort-
Demo” applet), which has been a part of the Java scene since the 1.0 release
(and is dated January 31, 1995). Figure 2.5 is a class diagram for the animated
sort algorithm. The SortItem class is the applet. None of these classes have

Figure 2.5 Dr. Gosling’s Animated Sort Algorithm

package statements; they are all members of the unnamed package.


When I first got the idea to use the animated sort algorithm as an example in
this section, my expectation was that the SortAlgorithm class would be
declared abstract. I was intrigued to find that it was not (Figure 2.5 is mis-
leading in this regard), and set about to analyze Dr. Gosling’s use of access
modifiers in this very popular applet. I will now show that the sort(int a[])
method in the SortAlgorithm class should have been declared public
abstract (which would have necessitated also declaring the SortAlgor-

SCOPE, SHADOWING, AND QUALIFIED ACCESS 319


ithm class abstract). The point of this exercise to take a close look at an exam-
ple in which members are more accessible than their class type.
Before continuing, however, allow me to point out the obvious. Because all
of these classes are members of the unnamed package, none of the methods in
the SortAlgorithm class actually need to be declared public or
protected . This observation is confirmed by the fact that the default access
sort(int a[]) method is invoked in the applet. It does not, however, detract
from my analysis of access control in the animated sort algorithm. Adding to the
confusion, two of the overriding sort(int a[]) methods in the Sort-
Algorithm subclasses are package-private while the third is declared
public . For me, however, this only adds to the intrigue. What was Dr. Gosling
thinking as he typed the access modifier for each of these methods? This is
where my analysis starts.
Here is a skeleton declaration of the SortAlgorithm class:

class SortAlgorithm {
protected boolean stopRequested = false;
public void setParent(SortItem p) { … }
protected void pause() throws Exception { … }
protected void pause(int H1) throws Exception { … }
protected void pause(int H1, int H2) throws Exception { … }
public void stop() { … }
public void init() { … }
void sort(int a[]) throws Exception { }
}

The ellipses indicate method implementations that have been deliberately omit-
ted. There are several details to notice about the sort(int a[]) method.
• It is package-private
• There is no default implementation. The sort(int a[]) method is coded
exactly as you see it here
• Dr. Gosling’s unusual parameter specifier style (please do not emulate him
in this case)
All of the SortAlgorithm class methods invoked by the applet are declared
public except for the sort(int a[]) method. Why declare them public?
And why treat the sort(int a[]) method differently? The answers to these

320 JAVA RULES


questions is what makes this example so interesting. From what I am able to sur-
mise, Dr. Gosling was using the following three levels of access control:
1. Methods invoked in the SortItem applet class and implemented in the
SortAlgorithm superclass are declared public.
2. Methods implemented in the SortAlgorithm superclass and invoked in
subclasses are declared protected. As discussed in 2.8.1 The
protected Access Modifier, when used to modify an instance variable or
instance method (as in this example), the protected access modifier
means nothing more or less than that the method is inherited by subclasses
in different packages. It really has nothing to do with access control. All of
the protected members in this example essentially have default access.
3. The sort(int a[]) method is the only remaining method. It is invoked in
the SortItem applet class, but implemented the SortAlgorithm sub-
classes.
Subclasses inherit both interface and implementation. The public and
protected access modifiers in the SortAlgorithm superclass are
essentially being used to separate interface from implementation. Is this
the best approach to access control using the Strategy design pattern? Maybe
not, but this code was written a long time ago only a matter of months after the
publication of the seminal work Design Patterns.
The sort(int a[]) method is the most important part of the interface
and should have been declared public. As coded, the sort(int a[])
method in the SortAlgorithm superclass has neither interface nor imple-
mentation. It actually never makes sense to declare a package-private
method that has an empty method implementation. Why? Because sub-
classes are not bound by the compiler-enforced method contract. In fact, they
are not even obligated to override the sort(int a[]) method, more or less
declare it public. If such a subclass were declared, the compiler would not
detect the problem. A NoSuchMethodError would be thrown at run time if
an attempt was made to use the unimplemented sort algorithm.
This is why the sort(int a[]) method in the SortAlgorithm super-
class should be declared abstract. As already stated, declaring it public is
not really required, but would be consistent with the use of access modifiers in

SCOPE, SHADOWING, AND QUALIFIED ACCESS 321


the other method declarations to separate implementation from interface. Doing
so would also allow the SortAlgorithm superclass to implement an inter-
face. It would also make the SortAlgorithm superclass an example of the
first bulleted item in the list at the top of this section (as it should be):
abstract classes that are extended, but never instantiated. As stated in that
bulleted item, the public methods in such a class are accessible outside of
the package in which they are declared only as inherited members of an
accessible subclass.
The prevailing design pattern at this time would be to implement a
SortAlgorithm interface in an abstract class named either Abstract-
SortAlgorithm (following Joshua Bloch’s lead in the Collection Framework)
or SortAlgorithmImpl. The name of such a class is not really important
because it is not seen outside of the package in which it is declared except possi-
bly by subclass programmers using the protected interface. The Strategy
design pattern practically begs for the superclass to implement an interface that
can be used by client programmers. Doing so tends to result in much more
thought being given to the interface design (which in turns leads to greater code
reuse).
The SortAlgorithm subclasses are examples of implementation-only
classes. Instances of these classes are passed out of the package in which they
are declared, but only as instances of the SortAlgorithm superclass (or
what really should be an interface). As stated in the second bulleted item in the
above list, the only public methods in an implementation-only class are those
that override public methods in the superclass or superinterface. Any local
and anonymous class an instance of which is returned by a public method is
an implementation-only class because local and anonymous classes are block-
private. That is 99.9% of all local and anonymous classes. (The unlikely alterna-
tive being a local or anonymous class that is actually only used in the block in
which it is declared.)
When declaring members in a private member class as well as in a local
or anonymous class, the only reason to use access modifiers is the compiler-
enforced method contract. The members of such a class are always acces-
sible. For example,

322 JAVA RULES


public class Test {
public static void main(String[] args) {
InnerClass inner = new Test().new InnerClass();
inner.print();
}
private class InnerClass {
private InnerClass() { } //same as default constructor
void print() {
System.out.println(
"a private inner class constructor was invoked");
}
}
}

Executing this program prints a private inner class constructor


was invoked. Why bother with access modifiers? Just use default access.
There is less clutter that way.

2.8.4 Accessing the Implementation of Same Class Objects


There is a substantial difference between accessing the members of the current
object versus another object of the same class. For example,

class Test {
public static void main(String[] args) {
Widget widget = new Widget();
new Widget().print(widget);
}
}
class Widget {
private String s = "this is the current instance";
public void print(Widget widget) {
widget.s = "this is another object of the same class";
System.out.println(s);
System.out.println(widget.s);
}
}

Executing this program prints

this is the current instance


this is another object of the same class

SCOPE, SHADOWING, AND QUALIFIED ACCESS 323


This first came to my attention when I read the following in Martin Fowler’s UML
Distilled:
I prefer not to access private or protected members of other
objects of the same class. Many others follow this (unstated) conven-
tion.72

This is indeed a special means of access. If an instance of the Widget


class is passed to any other class, that class could not assign a different value
to a private instance variable (as Widget does in this example). It is not dif-
ficult to imagine scenarios in which this particular instance enters an illegal state.

2.9 Encapsulation
This chapter has discussed two fundamentally different means of access, instan-
tiation and accessing the implementation of an object. When I think of encapsula-
tion, I think of the latter. The essential meaning of encapsulation is that the
instance variables in which the state of an object is stored are declared
private . That is why the JLS states that “well-designed classes have very few
public or protected fields, except for fields that are constants (final
static fields).”73 The JLS would be more correct to say well-designed
classes have very few non- private instance variables.

Access in the Java programming language is an all-or-nothing


proposition.

If someone has access to a field, it means not only that they can read the
value stored in that field, but they can also change the value. Therefore, any
code that has access to a field is potentially responsible for corrupting the data
stored in that field. If access to instance variables is uncontrolled (read non-
private ), the first step in debugging is to determine all of the classes in a sys-
tem that have access to the corrupted data. In large applications that might be

72. Martin Fowler, UML Distilled: Applying the Standard Object Modeling Language, (Read-
ing: Addison-Wesley, 1997), 101.
73. Gosling et al., §6.8.4, “Field Names.”

324 JAVA RULES


tens or even hundreds of classes. The problem can be likened to finding out
which legacy system programs are responsible for corrupt data in a file —after
that file has passed through the entire production process. On the other hand, if
instance variables are always declared private, the responsible programmer
need only look at his own code to figure out what is wrong. The problem may be
an argument passed to a method or constructor, but fixing such a problem is
merely a matter of adding additional argument checks. The additional argument
checks will expose the classes that were responsible for the corrupt data in the
first place. That is what encapsulation is all about. Encapsulation makes debug-
ging large, complex systems relatively easy.
The encapsulation of methods (versus instance methods) requires that the
responsible programmer differentiate between interface and implementation.
The public methods in a class type implicitly define the public interface
used by client programmers. The following is critical to your development as an
object-oriented programmer.

From an object-oriented design perspective, there is no difference


whatsoever between designing the interface implicitly defined
by the public methods in a class type and designing the
interface in an interface type. One should be as carefully
designed as the other.

A public methods is always part of some interface. Therefore the decision to


declare a method public is more a matter of interface design than it is access
control. To properly design an interface, you simply cannot be thinking about the
implementation of a class. You must think about the client programmer and seek
to find what I like to call the natural interface for that particular class of
objects.
It is becoming increasingly common to refer to public class and interface
types and their public members as exported. For example, Bloch defines the
term exported API in his book Effective Java. I really do not see the need for
this term, but you should at least be aware of it.

SCOPE, SHADOWING, AND QUALIFIED ACCESS 325


The so-called “protected interface” (I really do not like this term) is an API
designed to simplify the implementation of subclasses. I stress the word
implementation because the protected interface in no way defines a class
or interface type. It is not an “interface” in the object-oriented design sense of
the word. All we are talking about here is fields, methods, and member types

The protected keyword should always signify implementation in


the mind of an object-oriented programmer.

that are inherited by subclasses declared in other packages. The protected


members are inherited by subclasses but they cannot be used to “interface” with
(read send messages to) instances of the superclass. It is a grossly misleading
term in that sense. It really should be replaced by protected implementa-
tion, because that is what it is; a protected instance variable, instance
method, or constructor is part of the superclass implementation that is “pro-
tected” from the very subclasses that inherit it. Implementation dependencies
are a major concern when declaring an instance variable or instance method
protected . See 3.9 Designing Extensible Classes for a discussion of imple-
mentation dependencies related to the use of the protected access modifier.
The remaining access modifier is private, which is used to control
access from within a package. Table 2.4 summarizes encapsulation using

326 JAVA RULES


access control. As you can see, deciding which access modifier to use to con-

Table 2.4 Encapsulation Using Access Control


Access Modifier Use

public Used in designing the public interfaces (which may be


either class or interface types) used by client
programmers.

protected (instance Used in designing the protected interfaces used to


variables and instance simplify the implementation of subclasses in other
methods) packages. If the class is not extended outside of the
current package, then default access should be used
instead. (Always err on the side of encapsulation.)

protected (static Used to allow all subclasses access (including those


members and type privileges) declared in other packages).

default access Used to allow special access to members of the same


package. Otherwise, you should prefer private to limit
the implementation dependencies within a package.
(Always err on the side of encapsulation.)

private Used to control access from within a package. There are


four different meanings of private: class- private ,
containment-hierarchy-private, inner-class-hierarchy-
private, and block-private

trol access to the implementation of an object is pretty straightforward.


Making an entity less accessible is incompatible with existing binaries and can
result in an IllegalAccessError being thrown at run time. The program-
mer making such a change is always responsible for recompiling dependencies.
In the case of methods, changing the access modifier also changes the compiler-
enforced method contract. If the method was public before the change,
recompiling is the least of your concerns. You are substantially changing the API
design. Therefore, always err on the side of encapsulation. For example, if
you are unsure about whether or not a class will be extended in other packages,
cap the class hierarchy (i.e., declare the class final) and use default access
until there is a clear need to extend the class. Then you can remove the final
class modifier and declare some of the members protected without breaking

SCOPE, SHADOWING, AND QUALIFIED ACCESS 327


existing binaries. In other words, if possible defer the design of a protected
interface.

328 JAVA RULES


Chapter 3

Hiding and Inheritance

Chapter Contents
3.1 Introduction 329
3.2 Hiding 332
3.3 The Definition of Baseclass 340
3.4 The Definition of Related Classes 341
3.5 Generalization in Inheritance Hierarchies 342
3.6 Inheritance 344
3.6.1 Interface Inheritance 345
3.6.2 Implementation Inheritance 347
3.6.3 Inheriting Overloaded Methods 362
3.7 Do Interfaces Extend the Object Class? 362
3.8 Inheriting Members With The Same Name 367
3.8.1 Re-Inheritance 367
3.8.2 Ambiguous Names Related to Inheritance 368
3.8.3 Inheriting Methods With the Same Signature 370
3.9 Designing Extensible Classes 374
3.10 Capping a Class Hierarchy 387

3.1 Introduction
The meaning of hiding as defined in this chapter is consistent with changes
Gilad Bracha made in the Second Edition of the JLS. Specifically, hiding is not
the same as shadowing or obscuring as defined in the last chapter. Only mem-
bers are hidden, and they are always hidden by subclass members. The signifi-
cance of hiding a superclass member is that the hidden member is not inherited.
Hence, hiding is discussed before inheritance.

HIDING AND INHERITANCE 329


As used in object-oriented programming the term class is related to the verb
to classify, which the Cambridge International Dictionary of English defines as
“to divide (things) into groups according to type.”1 The same dictionary further
defines type as “a particular group of people or things which shares similar char-
acteristics and forms a smaller division of a larger set.”1 Objects have a class.
Types describe an entire class of objects. Types are further generalized into
hierarchies. The types in such a hierarchy are properly described as supertypes
and subtypes, but because the Java programming language has both class and
interface types, the terms superclass, subclass, superinterface, and sub-
interface are preferred. Classification in object-oriented programming is that
simple.
Somewhere along the line, however, superhuman efforts to discover the
essential meaning of the term inheritance as used in object-oriented program-
ing led a handful of well-meaning software engineers and technical writers to
stumble over phylogenetic classification into the use of biological taxonomy (also
known as the Linnaean system of binomial nomenclature) as examples in object-
oriented textbooks. In general usage, taxonomy is the science of classification
as applied to plants and animals (or more generally to living organisms as
opposed to inanimate objects). As shown in Figure 3.1, there are also classes in
biological taxonomy. What does all this have to do with computer programming?
The answer is little or nothing. Yet our understanding of a word is like a pic-
ture frame. Large pictures require large frames. The problem is that no explana-
tion of inheritance in object-oriented programming can begin to fill the “picture
frame” of phylogenetic classification. The danger is in trying. Stretching and pull-
ing on the lexicographical canvas, you can try to make inheritance in object-ori-
ented programming sound more complicated than it really is.
It is precisely because inheritance is so often made to sound more compli-
cated than it really is that this chapter discusses how inheritance is implemented
in a JVM, which I refer to as the inheritance mechanism. An understanding of
the inheritance mechanism is of interest to application programmers not only
because it demystifies inheritance, but also because it explains how hiding and

1. Cambridge International Dictionary of English online ( dictionary.cambridge.org)

330 JAVA RULES


Figure 3.1 The categories (or taxa) in biological taxonomy

overriding are implemented at the machine level. If you could watch a method
dispatch table being loaded, you could actually “see” how inheritance, hiding,
and overriding work: inaccessible superclass methods are not loaded into the
method dispatch table of a subclass, and therefore cannot be executed; hiding
and overriding methods overlay the entry in a method dispatch table in which the
hidden or overridden superclass method was previously loaded; etc.
In 3.4 The this and super Keywords in Volume 1, I said that understand-
ing how the this mechanism works “is like pulling the curtain back on the Wiz-
ard of Oz when it comes to understanding how object-oriented programming
languages work.” The same could be said for understanding how method dis-
patch tables are loaded. Once you know that inheritance is a factor of how
method dispatch tables are loaded, there is little more that even Dr. Gosling (the
Wizard at Sun) could say to make inheritance, hiding, and overriding any clearer
or easier to understand.

HIDING AND INHERITANCE 331


3.2 Hiding
Subclass members are said to hide accessible superclass members that have
the same simple name or method signature. Notice this says accessible. The
concept of hiding does not apply to inaccessible superclass members. There is
another requirement for hiding that is sometimes overlooked. Both entities are
always one of the following.
• Fields
• Class methods
• Member types
You never hear of a method hiding a field, or vice versa, because they are differ-
ent kinds of entities. Local variables or parameters never hide fields either. They
shadow them. The difference is that shadows pass. Once control passes out of
the method or constructor body in which a superclass entity is shadowed, that
entity is once again visible. A hidden entity, however, is not inherited. It is there-
fore never in scope in the subclass.
Instance methods are overridden, not hidden. Attempting to hide a class
method with an instance method or to override an instance method with a class
method generates a compiler error. Hiding class methods and overriding
instance methods are so radically different that they cannot even be compared.
The main difference is that hiding any superclass member (including class meth-
ods) is generally discouraged (for reasons that will be discussed momentarily),
whereas overriding instance methods is an essential part of any object-oriented
programming language. About the only thing that hiding and overriding have in
common is that both are based on method signatures, not just method names.
This means that overloaded methods must be hidden or overridden on a
per signature basis. Otherwise, they are inherited.
Another major difference between hiding and overriding is the relative ease
with which hidden entities are accessed. When instance methods are overridden,
the super.methodName general form invoked from within the body of the
overriding subclass is “the only way back.” There are three different ways to
access hidden fields and class methods:

332 JAVA RULES


1. The super keyword
2. Qualified names (static members only)
3. Casting a target reference to the superclass type
The super keyword can be used in subclasses, much as it is used to access an
overridden instance method. Qualified names are the TypeName.fieldName
and TypeName.methodName general forms, where TypeName is the super-
class type. This only works for class variables and class methods. Casting tar-
get references is discussed in 1.10.3 Casting a Target Reference. Both qualified
names and casting a target reference work in any class that has access to the
hidden field or class method.
The only consideration when hiding superclass fields is the field name. The
fact that the hidden field is a class variable or instance variable does not mat-
ter; nor does the type of the hidden field matter. For example,

class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
sub.print();
System.out.println(sub.s);
System.out.println(((Superclass)sub).s);
}
}

class Superclass {
static String s = "superclass";
int x = 0;
}

class Subclass extends Superclass {


String s = "subclass";
static final double x = Math.PI;
void print() {
System.out.println(x);
System.out.println(super.x);
}
}

Executing this program prints:

HIDING AND INHERITANCE 333


3.141592653589793
0
subclass
superclass

This example shows that an instance variable can hide a class variable, and that
a double can hide an int. These are not gratuitous examples. It is difficult to
imagine why someone would hide just the value of a superclass field by declar-
ing a subclass field with the same field modifiers, type, and identifier. In fact, it is
difficult to image any reason for hiding a field. In order for a field to be hidden, it
must first be accessible, so what we are talking about here is non-private
fields. Furthermore, the field would have to be non-final. If you understand
protected access, hiding a protected field makes no sense whatsoever.
A public, non-final field is a rare bird indeed (because of the need for
encapsulation). That leaves only default access. The remainder of this section
will take a close look at the problem with hiding default access fields before
explaining why hiding is allowed at all.
When discussing the hiding of instance variables, it is important to under-
stand that field access expressions are not polymorphic. In the primary-
Expression.fieldName general form, the type of the variable or other pri-
mary expression is always used. As stated in the JLS:
Note, specifically, that only the type of the Primary expression, not
the class of the actual object referred to at run time, is used in deter-
mining which field to use.2

Thus assigning a subclass object to a superclass field does not change the
result of a field access expression. Here is a simplified version of the example in
the JLS:

class Test {
public static void main(String[] args) {
T t = new T();
S s = new S();
System.out.println("t.x = " + t.x);

2. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java Language Specification,
Second Edition (Boston: Addison-Wesley, 2000), §15.11.1, “Field Access Using a Primary.”

334 JAVA RULES


System.out.println("s.x = " + s.x);
s = t;
System.out.println("s.x = " + s.x);
}
}
class S { int x = 0; }
class T extends S { int x = 1; }

Executing this program prints

t.x = 1
s.x = 0
s.x = 0

The assignment s = t does not change the fact that the type of s is S. Hence
the value accessed is still zero.
I am using the examples in the JLS for a very deliberate reason. The JLS
goes on to make the point that “the power of late binding and overriding is avail-
able in, [sic] but only when instance methods are used.”3 Here again is a simpli-
fied version of the next example in the JLS:

class Test {
public static void main(String[] args) {
T t = new T();
S s = new S();
System.out.println("t.z() = " + t.z());
System.out.println("s.z() = " + s.z());
s = t;
System.out.println("s.z() = " + s.z());
}
}
class S { int x = 0; int z() { return x; } }
class T extends S { int x = 1; int z() { return x; } }

Executing this program prints:

t.z() = 1
s.z() = 0
s.z() = 1

3. Gosling et al., §15.11.1, “Field Access Using a Primary.” Gilad Bracha cut a bunch of “in Java”
when working on the Second Edition. Here the “in” didn’t get cut.

HIDING AND INHERITANCE 335


So far so good, but the JLS completely ignores the real difficulty with hiding. For
example,

class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
System.out.println(((Superclass)sub).getS());
System.out.println(sub.getS());
}
}
class Superclass {
String s = "superclass";
String getS() {
return s;
}
}
class Subclass extends Superclass {
String s = "subclass";
//String getS() {
// return s;
//}
}

The type of sub is Subclass, so you might expect sub.getS() to print


subclass (the value of s in Subclass), but this is not the case. Executing
this program prints:

superclass
superclass

Likewise, if the getS() method in Subclass is uncommented, you might


expect ((Superclass)sub).getS() to print superclass (the value of
s in Superclass). It does not. Executing this program with the getS()
method in Subclass uncommented prints:

subclass
subclass

These examples are counterintuitive. In fact, the evaluation of Bug Id 4102718


(which is an RFE for compiler warning when fields are hidden) says that “this
behavior…is confusing and pedagogically tricky.” This behavior is unlike any-

336 JAVA RULES


thing else in the Java programming language. In order to understand these
examples you must consider the compile-time declaration, not the type of
the primary expression or the class of the object referenced. If the compile-time
declaration is found in Superclass, then the value of s is superclass
because that is the only s field that exists in Superclass. If it is found in
Subclass, then the value of s is subclass because the s field in
Superclass is hidden. Examples such as these are precisely why hiding
should not be used, not even for default access fields.
The JLS makes a very interesting comment while explaining that local vari-
ables cannot shadow method parameters:
If a declaration of an identifier as a local variable of the same method,
constructor, or initializer block appears within the scope of a parame-
ter or local variable of the same name, a compile-time error occurs…

This restriction helps to detect some otherwise very obscure bugs. A


similar restriction on shadowing of members by local variables was
judged impractical, because the addition of a member in a superclass
could cause subclasses to have to rename local variables.4

This should give the programmer some idea about what the authors think about
the practice of shadowing fields in a method. Indeed, the JLS goes on to say, “it
is considered poor style to have local variables with the same names as fields.”5
What it does not say is that the designers of the Java programming language
wanted to make hiding of superclass fields a compiler error. You can see that in
the following quote from The Java Programming Language (which Dr. Gos-
ling coauthored).
…where fields are concerned, it is hard to think of cases in which hid-
ing them is a useful feature.

Hiding is allowed in Java because implementors of existing super-


classes must be free to add new public or protected fields with-
out breaking subclasses. If the language forbade using the same field

4. Gosling et al., §14.4.2, “Scope of Local Variable Declarations.”


5. Gosling et al., §14.4.3, “Shadowing of Names by Local Variables.”

HIDING AND INHERITANCE 337


name in a superclass and a subclass, adding a new field to an existing
superclass could potentially break any of the subclasses already using
those names.

If adding new fields to existing superclasses would break some


unknown number of subclasses, you’d be effectively immobilized,
unable to add public or protected fields to a superclass. Purists
might well argue that classes should have only private data, but you
get to decide your style. 6

You need to appreciate that if these are not Dr. Gosling’s own words, then they
at least come with his stamp of approval. Therefore we conclude the following.

The hiding of fields is allowed not because it is a desirable language


feature but because it is a requirement of binary compatibility. As a
general rule, you should never hide or shadow superclass fields.

The binary compatibility problem that would exist in superclasses if hiding were
not allowed can still be seen in accessor methods. Suppose the following Sub-
class were a “pre-existing binary.”

class Subclass extends Superclass {


private double x = Math.PI;
public double getX() {
return x;
}
}

Look what happens if Superclass adds an accessor method with the same
signature:

class Superclass {
private int x = 0;
public int getX() {
return x;

6. Ken Arnold, James Gosling, and David Holmes, The Java Programming Language, Third
Edition (Boston, Addison-Wesley Professional, 2000), §3.3.3, “Accessing Inherited Members.” This
quote has been part of the book since the First Edition, which means it was certainly at least care-
fully read by Dr. Gosling.

338 JAVA RULES


}
}

The next attempt to compile Subclass generates the following compiler error:

Subclass.java:3: getX() in Subclass cannot override getX() in


Superclass; attempting to use incompatible return type
found : double
required: int
public double getX() {
^
1 error

I realize these are instance methods. The point is to show why hiding must be
allowed so as to not break subclasses.
Hidden fields and class methods are members of the direct superclass. Thus
the super.fieldName and super.methodName general forms are said
to access hidden superclass members. It is interesting to note, however, that if
a superclass field or class method is not hidden, then the super.fieldName
and super.methodName general forms are the same as this.field-
Name and this.methodName. For example,

public class Test {


String s = "Hello World!";
public static void main(String[] args) {
Test test = new Test();
Subclass sub = new Subclass("Goodbye Cruel C++ World!");
System.out.println("((Test)sub).s = " +
((Test)sub).s);
}
}
class Subclass extends Test {
public Subclass(String s) {
this.s = s;
System.out.println("this.s = " + this.s);
System.out.println("super.s = " + super.s);
}
}

Executing this program prints

HIDING AND INHERITANCE 339


this.s = Goodbye Cruel C++ World!
super.s = Goodbye Cruel C++ World!
((Test)subclass).s = Goodbye Cruel C++ World!

When super is used to access an instance variable or instance method, this


is implicitly used as the target reference. In this example, s is inherited. Thus
this.s and super.s refer to the same instance variable, which is a mem-
ber of the current class, not the direct superclass (as the super keyword
implies). Likewise, casting the target reference under these circumstances
accomplishes nothing.

3.3 The Definition of Baseclass


The term baseclass is not used in the JLS, which is why I use a separate section
to formally define it. I feel strongly that baseclass should be spelled as one
word, the same as superclass and subclass. In a single inheritance object-ori-
ented programming language such as Java, the term baseclass (as defined in
this section) can be just as important as superclass or subclass. Because of
single inheritance, the Object class is at the top of all class hierarchies. The
problem with this is that hierarchies are generally named after the class or inter-
face at the top of the hierarchy. To solve this problem, the term baseclass is
defined as direct subclasses of Object. The rule for naming class hierarchies
is stated as follows.

Class hierarchies in a single inheritance, object-oriented program-


ming language such as Java are named after their baseclass.

340 JAVA RULES


For example, the class hierarchy in Figure 3.2 is referred to as the Computer
class hierarchy:

Figure 3.2 Example of a baseclass

This term is useful in discussions about object-oriented design. For example, in a


number of design patterns such as the Strategy design pattern, the baseclass is
abstract. There is no equivalent term for interface hierarchies because inter-
faces support multiple inheritance. Note that some programmers use baseclass
as a synonym for superclass. I am not one of them. Having only one baseclass
in any given class hierarchy is consistent with the definition of base.

3.4 The Definition of Related Classes


This is another term not used in the JLS, but nevertheless very commonly used
by software engineers and technical writers. The danger of using related
classes is that the reader may not appreciate how precisely the term is defined.

HIDING AND INHERITANCE 341


Two classes are related if one is an extension of the other. For example, all of
the shaded boxes in Figure 3.3 are related classes.

Figure 3.3 An example of related classes

Classes in different class hierarchies are obviously not related. As this example
shows, however, classes in the same class hierarchy can also be unrelated. This
makes the term related classes somewhat counterintuitive because in a compa-
rable family tree all of the family members would be related. The terms related
and unrelated classes are particularly important when discussing type conver-
sion. For example, the only permitted conversions between class types are
those between related classes, which means that the classes are necessarily
part of the same class hierarchy.

3.5 Generalization in Inheritance Hierarchies


The terms class hierarchy and interface hierarchy always imply an inherit-
ance hierarchy. Inheritance hierarchies are used to represent the relationships
between supertypes and subtypes. The JLS uses the terms superclass,

342 JAVA RULES


superinterface, subclass, and subinterface rather than supertype and sub-
type. In fact, the terms supertype and subtype are found nowhere in the JLS.
The same could be said for the JVMS were it not for a single use of the term
supertype. Most books on object-oriented programming languages, however,
do use these terms. Table 3.1 list some of the other commonly used terms for
superclasses and subclasses.

Table 3.1 Alternative Terminology for Discussing Inheritance Hierarchies


Standard Terminology Alternative Terms

superclass supertype
superinterface parent
ancestor
base classa

subclass subtype
subinterface child
descendant
derived class
a. As defined in this book, baseclass is not a synonym for superclass. Baseclasses are always a direct sub-
class of Object. It is also spelled as one word.

Note that superinterface is used to describe the interfaces implemented by a


class type as well as superinterfaces in an interface hierarchy. As stated in the
JLS:
The optional implements clause in a class declaration lists the
names of interfaces that are direct superinterfaces of the class being
declared.

An interface type I is a superinterface of class type C if any of the fol-


lowing is true:

• I is a direct superinterface of C.
• C has some direct superinterface J for which I is a super-
interface…
• I is a superinterface of the direct superclass of C.
A class is said to implement all its superinterfaces.7

HIDING AND INHERITANCE 343


This needs to be pointed out because otherwise there is a tendency to think of
superinterfaces as only describing interfaces in an interface hierarchy. Likewise,
the term supertype in a class hierarchy refers to superinterfaces as well as
superclasses. Other than baseclass (as defined in this chapter), supertype, and
subtype , I do not use any of the other alternative terms.
When type hierarchies are considered as a whole, types at the top of the
class hierarchy are said to be more general. The UML defines generalization
as follows:
A taxonomic relationship between a more general element and a more
specific element. The more specific element is fully consistent with the
more general element and contains additional information. 8

More general and more specific are the preferred terms with which to discuss
the relationships between supertypes and subtypes in a class or interface hierar-
chy. However, there are alternative terms for generalization:
• abstraction
• derivation
• specialization
• restriction
Any class is an abstraction. Derivation, specialization, and restriction emphasize
subtypes: subclasses are “derived” from their superclass; a subclass is “a spe-
cial kind of” its direct superclass; and subclasses represent a “restriction” of
their superclass behavior.

3.6 Inheritance
A class inherits from the direct superclass either explicitly named in the
extends clause of a class header or implicitly from the Object class, as well
as from any direct superinterfaces named in the implements clause. An inter-
face inherits from any direct superinterfaces named in the extends clause of
an interface header.

7. Gosling et al., §8.1.4, “Superinterfaces.”


8. UML Semantics, Appendix M1-UML Glossary, version 1.0 dated 13 January 1997 (Santa Clara:
Rational Software, 1997), 7.

344 JAVA RULES


Classes inherit both interface and implementation. The term implementa-
tion inheritance refers to the inheritance of fields or methods. The implication
is that the subclass does not hide or override those fields and methods. Unlike
implementation inheritance, the term interface inheritance does not refer to
the inheritance of individual fields or methods. In fact, the entire reason for
differentiating interface inheritance from implementation inheritance is to
emphasize that subclasses inherit types (both class and interface types),
not just individual fields and methods. For example, the Integer class

Interface inheritance is all about supertypes (both class and interface


types), not individual fields or methods.

inherits the Number interface (a class type), and GregorianCalendar


inherits the Calendar interface (also a class type). The interfaces inherited by
a given class of objects are referred to as their supertypes. Non-abstract
classes necessarily implement all of their supertypes. Thus an instance of the
class can be assigned to any variable that has a superclass or superinterface
type. Supertypes are the “many forms” referred to by the term polymor-
phism. See 5.4 Substitution is a Higher Concept than Polymorphism for a defini-
tion of polymorphism.

3.6.1 Interface Inheritance


The implements clause in a class declaration is a comma-separated list of
the direct superinterfaces. A direct superinterface is analogous to a direct
superclass, except that a class can implement any number of direct superinter-
faces. Knowing what superinterfaces are implemented by any given class of
objects is complicated by several factors:
• There is multiple inheritance in interface hierarchies
• Any class anywhere in a given class hierarchy can implement an unlimited
number of interfaces
• A class with no implements clause may in fact implement any number of
interface types

HIDING AND INHERITANCE 345


• An interface type not named in any implements clause anywhere in a
class hierarchy may nonetheless be implemented because it is a superinter-
face of an interface type that is named in an implements clause
All combined these can make for some intricate relationships between a class
and the superinterfaces it implements. This is precisely why javadoc lists
“All Implemented Interfaces” in the API docs for a class type. In addition, a
subclass inherits the interface implicitly defined by the public methods in
superclass types, including the Object type.
The most effective way to demonstrate how interface inheritance works is to
instantiate a subclass that has no implements clause and yet implements a
number of different supertypes inherited from both superclasses and superinter-
faces. For example,

class Test {
public static void main(String[] args){
Subclass sub = new Subclass();
if (sub instanceof Object &&
sub instanceof Superclass &&
sub instanceof Subclass &&
sub instanceof Multiple &&
sub instanceof Inheritiance &&
sub instanceof Superinterface &&
sub instanceof Subinterface)
System.out.println("this is an example of " +
"interface inheritiance");
}
}
interface Multiple {}
interface Inheritiance {}
interface Superinterface extends Multiple, Inheritiance {}
interface Subinterface extends Superinterface {}
class Superclass implements Subinterface {}
class Subclass extends Superclass {}

Executing this program prints this is an example of interface


inheritiance. Notice that there is no implements clause in the Sub-
class declaration, and yet Subclass implements no less than seven differ-
ent supertypes because of interface inheritance.

346 JAVA RULES


As a final note on interface inheritance, classes cannot implement nested
interfaces declared in the body of the same class. For example,

class Test implements Test.Parameters {


interface Parameters { }
}

Attempting to compile this program generates the following compiler error:

test.java:2: cyclic inheritance involving Test


interface Parameters { }
^
1 error

This compiler error is the result of a bug fix. There is a very funny comment in
the evaluation of the primary Bug Id:
There is an unfortunate specification vaccuum [sic] in this area -- inter-
actions of inner classes and inheritance range from purely nonsensical
to merely pathological. We will likely draw the line to favor simplicity
and avoidance of pathologies as opposed to assigning meaning to
every case possible.9

Actually, the range was a little wider than is described here. Jon Steelman
exploited this language feature in his “Java Tip 50: On the Parameter Constants
pattern” in JavaWorld.10 This article was an important contribution to the evolu-
tion of enumerated types in the Java programming language. The interface type
can always be imported. The only significant difference is that subclasses may
have to either import or implement the same interface because the members will
not be inherited.

3.6.2 Implementation Inheritance


All superclass and superinterface members are inherited by subclasses except
the following:

9. Evaluation of Bug Id 4087020 at developer.java.sun.com/developer/bugParade/


bugs/4087020.html. This emphasis on simplicity at Sun is what has made Java technology so
successful.
10. Jon Steelman, “Java Tip 50: On the Parameter Constants pattern,” (New York, JavaWorld,
2002), www.javaworld.com/javaworld/javatips/jw-javatip50.html .

HIDING AND INHERITANCE 347


• Inaccessible members
• Hidden fields (or interface constants), class methods, and nested types
• Overridden instance methods
The remainder of this section discusses each of these in order followed by an
elaborate example of implementation inheritance.
The term accessible was defined in the last chapter. When discussing inher-
itance (versus accesses members), the accessibility of types is not an issue. An
inaccessible class cannot be named in an extends clause of a class header;
nor can an inaccessible interface be named in the implements clause. There-
fore we take for granted the accessibility of the types from which members are
inherited. If a member is neither hidden nor overridden, the following rules apply.
• Members declared private are never inherited
• Members declared public or protected are always inherited
• Default access members are inherited only if the subclass is a member of
the same package
Inside of a package, all members that are not private are inherited. Outside
of a package, only public and protected members are inherited. This cre-
ates an interesting situation in which default access members are inherited by
subclasses declared in the same package, but are not inherited by subclasses
declared in different packages. I will revisit this issue in 3.9 Designing Extensible
Classes.
The introduction of the term shadowing in the Second Edition of the JLS is
most useful in discussing the fact that hidden fields are not inherited. For exam-
ple, this book used to include the following.
…superclass and superinterface fields are inherited if they are only hid-
den by local variable declarations or parameters. In other words, to say
a field is not inherited because of hiding implies that it is hidden by
another field.

Now all we have to say is that hidden fields are not inherited, just like any other
kind of entity.
In 3.2 Hiding, I said that hidden instance variables are considered members
of the direct superclass. This point is worth elaborating on in order to clarify the

348 JAVA RULES


definition of a member. To say that hidden instance variables are members of
the direct superclass is somewhat counterintuitive because, as explained in 4.11
The Object Class in Volume 1, hidden instance variables are implemented in
subclass objects. Here is a simplified version of the example in that section:

class Test {
public static void main(String[] args) {
Subclass subclass = new Subclass();
System.out.println(subclass.s);
System.out.println(((Superclass)subclass).s);
}
}

class Superclass {
String s = "superclass";
}

class Subclass extends Superclass {


String s = "subclass";
}

Executing this program prints

subclass
superclass

The Subclass only has one member, yet the value of two different fields is
printed using the same subclass object. One of those fields is a member of
Superclass. All hidden fields or class methods are members of the
direct superclass. This becomes a very significant point when discussing the
meaning of the protected access modifier.
There is nothing remarkable about the fact that overridden instance methods
are not inherited. For example,

class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
System.out.println(sub.a());
}
}

HIDING AND INHERITANCE 349


class Superclass {
String a() {
return "overridden instance method";
}
}

class Subclass extends Superclass implements Interface {


String a() {
return "overridden instance methods are not inherited";
}
}

Executing this program prints overridden instance methods are not


inherited . The definition of overriding is such that you would expect as much.
The most effective way to demonstrate how inheritance works is to instanti-
ate a subclass that has no declared members, and then use the object created
to print the value of inherited fields and to invoke inherited methods. For example,

class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
Subclass.Top top = new Subclass.Top();
Subclass.Inner inner = sub.new Inner();

System.out.println(sub.a);
System.out.println(sub.b);
System.out.println(sub.c());
System.out.println(sub.d());
System.out.println(sub.e);
System.out.println(top.getS());
System.out.println(inner.getS());
}
}

class Superclass {
static String a = "Subclasses inherit class variables";
String b = "Subclasses inherit instance variables";
static String c() {
return "Subclasses inherit class methods";
}
String d() {
return "Subclasses inherit instance methods";

350 JAVA RULES


}
class Inner {
private String s = "Subclasses inherit inner member classes";
public String getS() {
return s;
}
}
static class Top {
private String s = "Subclasses inherit nested top-level" +
" classes";
public String getS() {
return s;
}
}
}
interface Interface {
String e = "Subclasses inherit interface constants";
}

class Subclass extends Superclass implements Interface { }

Executing this program prints

Subclasses inherit class variables


Subclasses inherit instance variables
Subclasses inherit class methods
Subclasses inherit instance methods
classes inherit interface constants
Subclasses inherit nested top-level classes
Subclasses inherit inner member classes

This example demonstrates that although the definition of Subclass class is


empty, there are seven members in that class.
Unless shadowed by other declarations, inherited members are referred to
using simple names (which is to say inherited members are in scope). As stated
in the JLS:
The scope of a declaration of a member m declared in or inherited by a
class type C is the entire body of C, including any nested type declara-
tions.11

11. Gosling et al., §8.1.5, “Class Body and Member Declarations.”

HIDING AND INHERITANCE 351


Notice this says “declared in or inherited by.” Except for the fact that inherited
members are declared in superclasses or superinterfaces, they are no different
than declared members. If you were to pluck the declaration of an inher-
ited member out of the superclass or superinterface and put it in the
subclass, it would not change how that entity can be used in the subclass
one iota.
The remainder of this section discusses how the inheritance mechanism
works for methods. The purpose of this explanation is twofold. It shows that
inheritance in object-oriented programming is not nearly as complex as you
might think. At the same time it shows how hiding class methods and overriding
instance methods are implemented at the machine level. This is a wonderfully
simple mechanism.
The JLS only mentions method dispatch tables once, in a discussion of
overriding (or dynamic method lookup):
We note that the dynamic lookup process, while described here explic-
itly, will often be implemented implicitly, for example as a side-effect of
the construction and use of per-class method dispatch tables, or the
construction of other per-class structures used for efficient dispatch. 12

Method dispatch tables have always been used in Sun implementations. I think it
is safe to say that all commercial JVM implementations use them. There is only
one such table for each class (which is why they are sometimes referred to as
per-class method dispatch tables). Every method that can be invoked for a
given class of objects can be found in the per-class method dispatch table for
that class, including all of the methods inherited from superclasses. Thus a sin-
gle table lookup is required to resolve symbolic references to methods in that
class. This dramatically improves the overall efficiency of a JVM. Without the use
of per-class method dispatch tables, classes would have to be searched recur-
sively starting at the class of the target object and working up the class hierar-

12. Gosling et al., §15.12.4.4, “Locate Method to Invoke.” As always, runtime data structures are
implementation defined. This discussion assumes the use of per-class method dispatch tables
(which I am reasonably sure is the case in all Sun implementations).

352 JAVA RULES


chy to the Object class until a method is found. Doing so would be very
inefficient.
The example of loading a method dispatch table in this section is for the
LineNumberReader class in the java.io package. This has no significance
whatsoever beyond that fact that Table 3.5 Fully Loaded LineNumberReader
Method Dispatch Table on page 360 fits on a single page (both in terms of the
page length and width). The method descriptors for LineNumberReader are
short because the result types and parameters are mostly primitive data types
(resulting in a manageable table width); plus there are a limited number of default
access and private methods (resulting in a manageable table length).
LineNumberReader is an instance of BufferedInputReader,
which in turn extends the abstract Reader baseclass. This is a lengthy
example because I think it is important for the reader to actually see one of
these tables as it is being loaded. Therefore I stop after loading each of the
three superclasses in order to explain some of the details of loading method dis-
patch tables. The following abreviated class declarations include all of the meth-
ods in Object, Reader, BufferedInputReader, and LineNumber-
Reader. The throws clauses are omitted in order to make the listings more
readable. It just so happens that most of these methods are public, but there
are a few default access and private ones that you will not see in the API
docs.

package java.lang;
public class Object {
private static native void registerNatives();
public final native Class getClass();
public native int hashCode();
public boolean equals(Object obj)
protected native Object clone()
public String toString()
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout)
public final void wait(long timeout, int nanos)
public final void wait()
protected void finalize()
}

HIDING AND INHERITANCE 353


package java.io;
public abstract class Reader {
public int read()
public int read(char cbuf[])
public abstract int read(char cbuf[], int off, int len)
public long skip(long n)
public boolean ready()
public boolean markSupported()
public void mark(int readAheadLimit)
public void reset()
public abstract void close()
}

package java.io;
public class BufferedReader extends Reader {
private void ensureOpen()
private void fill()
public int read()
private int read1(char[] cbuf, int off, int len)
public int read(char cbuf[], int off, int len)
String readLine(boolean skipLF)
public String readLine()
public long skip(long n)
public boolean ready()
public boolean markSupported()
public void mark(int readAheadLimit)
public void reset()
public void close()
}

package java.io;
public class LineNumberReader extends BufferedReader {
public void setLineNumber(int lineNumber)
public int getLineNumber()
public int read()
public int read(char cbuf[], int off, int len)
public String readLine()
public long skip(long n)
public void mark(int readAheadLimit)
public void reset()
}

354 JAVA RULES


The methods that actually end up in the fully loaded LineNumeberReader
method dispatch table are marked in bold. You should notice two things right
away. The first is that LineNumberReader is a poor example because it
does not override any of the housekeeping methods in the Object class, not
even the toString() method. Thus converting a LineNumberReader to a
sting prints something like java.io.LineNumberReader@7182c1. The
other thing is that all of the methods declared in LineNumberReader are
loaded (as is always the case for the class for which the method dispatch table
is created). I do not give much thought as to the order in which methods are
loaded for a given class. I suspect they are loaded in the same textual order as
they appear in the source code. At any rate, the order in which methods are
loaded for a given class is of no consequence to this discussion.
Method dispatch tables are always loaded starting with the Object class
and working down a class hierarchy. A method that is not inherited is physically
not in the method dispatch table. As stated above, all superclass and superinter-
face members are inherited by subclasses except the following.
• Inaccessible members
• Hidden fields (or interface constants), class methods, and nested types
• Overridden instance methods
As a method dispatch table is loaded, the access modifier of superclass meth-
ods is read to determine if the method is accessible from the subclass. Inacces-
sible superclass methods are simply not loaded.
Because methods in the Object class are loaded first, the method dis-
patch table for LineNumberReader as well as all other classes begin life as
shown in Table 3.2.13 The key for a method dispatch table is the method signa-
ture and result type. This is referred to as the method descriptor. Notice that
the private registerNatives() method is not loaded. Method dispatch
tables include private methods, but only if they are declared in the class that

13. The dynamically created arrays classes actually use the same method dispatch table as the
Object class (at least in Sun implementations) because they have no methods of their own, only
the length field.

HIDING AND INHERITANCE 355


Table 3.2 Partially Loaded LineNumberReader Method Dispatch Tablea
Access
Method Descriptor (The Key) Modifier final
getClass()Ljava/lang/Class; public final
hashCode()I public

equals(Ljava/lang/Object;)B public
clone()Ljava/lang/Object; protected
toString()Ljava/lang/String; public
notify()V public final

notifyAll()V public final


wait(I)V public final
wait(II)V public final
wait()V public final
finalize()V protected
a. There are many columns in an actual method dispatch table, most of which are omitted in these examples.
The method descriptor (or key) and access modifier are certainly two of those. I am not so certain about the
final modifier. It is included in these examples for instructional purposes.

uses the table. In this case that means only the private methods in Line-
NumberReader are loaded.
Table 3.3 shows the method dispatch table after loading methods from the
abstract Reader baseclass. Reader does not override any of the house-

Table 3.3 Partially Loaded LineNumberReader Method Dispatch Table


Access
Method Descriptor (The Key) Modifier final
getClass()Ljava/lang/Class; public final
hashCode()I public
equals(Ljava/lang/Object;)B public
clone()Ljava/lang/Object; protected

356 JAVA RULES


Table 3.3 Partially Loaded LineNumberReader Method Dispatch Table
Access
Method Descriptor (The Key) Modifier final
toString()Ljava/lang/String; public
notify()V public final

notifyAll()V public final


wait(I)V public final
wait(II)V public final
wait()V public final

finalize()V protected
read()I public
read([C)I public
read([CII)I public
skip(J)J public
ready()B public
markSupported()B public
mark(I)V public
reset()V public
close()V public

keeping methods in the Object class, so the method dispatch table is merely
extended. Note that abstract methods are loaded. One of the columns in
method dispatch tables that I am not showing is the abstract method modi-
fier. A JVM will throw AbstractMethodError if such a method is invoked in
a fully loaded method dispatch table. That cannot happen in the LineNumber-
Reader method dispatch table because all of the abstract methods loaded
from the abstract Reader baseclass are overlaid (read overridden) when
loading methods from BufferedInputStream.

HIDING AND INHERITANCE 357


Now we are ready to load methods from BufferedInputReader. This
is when things begin to get interesting because BufferedInputReader
overrides many of the methods in the Reader baseclass. As methods are
loaded into a method dispatch table, the table is searched from top to bottom to
see if a method with the same descriptor is already in the table. If so, that
method is overlaid with the subclass method. Does that mean that the overlaid
method does not exist? Effectively, yes, but only in the LineNumberReader
method dispatch table. The super.methodName general form can always be
used to invoke the hidden or overridden method. The super invocation mode
always uses the method dispatch table for the direct superclass.
As I said at the start of this section, the mechanism for hiding and overriding
is “wonderfully simple.” By overlaying an entry with the same method descriptor,
the superclass method is effectively hidden (static methods) or overridden
(non-static methods). The main column that actually gets overlaid is the
address of the bytecodes that implement the method. That column is not
shown in these examples. Table 3.4 shows the LineNumberReader method
dispatch table after loading methods from the BufferedInputReader
superclass. This time both the new entries and those that were overlaid are in

Table 3.4 Partially Loaded LineNumberReader Method Dispatch Table


Access
Method Descriptor (The Key) Modifier final
getClass()Ljava/lang/Class; public final
hashCode()I public
equals(Ljava/lang/Object;)B public
clone()Ljava/lang/Object; protected

toString()Ljava/lang/String; public
notify()V public final
notifyAll()V public final
wait(I)V public final

358 JAVA RULES


Table 3.4 Partially Loaded LineNumberReader Method Dispatch Table
Access
Method Descriptor (The Key) Modifier final
wait(II)V public final
wait()V public final

finalize()V protected
read()I public
read([C)I public
read([CII)I public

skip(J)J public
ready()B public
markSupported()B public
mark(I)V public
reset()V public
close()V public
readline(B)Ljava/lang/String;
readline()Ljava/lang/String; public

bold. The overloaded readline methods added to the end of the table are the
only new methods. All of the other rows in bold override instance methods that
were already in the table.
Finally, classes from the LineNumberReader class are loaded, including
all of the private methods. It just so happens, however, that there are no
private methods in the LineNumberReader class, only public ones
(and all but two of those override methods already in the table). After loading the
methods in LineNumberReader, the method dispatch table includes an entry
for every method that can be invoked given a reference to a LineNumber-
Reader object. Table 3.5 shows the fully loaded LineNumberReader
method dispatch table. The final keyword is interesting in terms of how hiding

HIDING AND INHERITANCE 359


Table 3.5 Fully Loaded LineNumberReader Method Dispatch Table

Method Descriptor (The Key) Access Modifier final


getClass()Ljava/lang/Class; public final
hashCode()I public
equals(Ljava/lang/Object;)B public
clone()Ljava/lang/Object; protected
toString()Ljava/lang/String; public
notify()V public final

notifyAll()V public final


wait(I)V public final
wait(II)V public final
wait()V public final
finalize()V protected
read()I public
read([C)I public
read([CII)I public
skip(J)J public
ready()B public
markSupported()B public
mark(I)V public
reset()V public
close()V public
readline(B)Ljava/lang/String;
readline()Ljava/lang/String; public
setLineNumber(J)V public
getLineNumber()I public

360 JAVA RULES


and overriding is implemented. A final method is literally the final (or last)
method for a given method descriptor that can be loaded into a method dispatch
table. It is therefore impossible to hide of override such a method. Though this
restriction is actually implemented in the compiler, it helps to understand the
keyword better (just like the extends keyword literally extends a method dis-
patch table).
The loading of per-class tables used to resolve symbolic references to class
variables is comparable to method dispatch tables, but inaccessible and hidden
instance variables are another matter altogether. An object includes every
instance variable declared in each of the superclasses, including inaccessible
and hidden instance variables not inherited by the subclass. Otherwise, the inher-
ited superclass methods that use those inaccessible and hidden instance vari-
ables could not be invoked. For example,

class Superclass {
private String s = "Hello World!";
void print() {
System.out.println(s);
}
}
class Test extends Superclass {
public static void main(String[] args) {
new Test().print(); //private members are not inherited
}
}

This program compiles and when executed prints "Hello World!". The
Test object created therefore must include a field named s even though that
field is not inherited. Even if the superclass method is hidden or overridden (and
therefore not inherited), it can still be invoked in the subclass by using the
super.methodName general form or by casting to the superclass type. In
either of these scenarios inaccessible and hidden instance variables must
be implemented in the subclass object. Understanding this is central to your
understanding of how object-oriented programming languages work. See 4.11
The Object Class in Volume 1 for a detailed discussion.

HIDING AND INHERITANCE 361


3.6.3 Inheriting Overloaded Methods
As stated in the JLS, “methods are overridden on a signature-by-signature
basis.”14 For a subclass to fully override an overloaded superclass method, it
must override each of the method signatures inherited. Any signature not over-
ridden or implemented is inherited. Likewise, if an interface overloads a method
name with n number of different method signatures, a class that implements
that interface must implement n number of methods with identical signatures.

3.7 Do Interfaces Extend the Object Class?


One of the quiet changes that took place in the Second Edition of the JLS made
me think of the following quote at the end of the preface:

This is the FEMALE EDITION of the Dictionary.

The MALE edition is almost Identical. But NOT quite.


Be Warned the ONE PARAGRAPH is crucially different.

The choice is yours.


— Milorad Pavic, Dictionary of the Khazars, Female Edition

My choice is the following paragraph from 9.2 Interface Members:


The members of an interface are those members inherited from direct
superinterfaces and those members declared in the interface.15

This paragraph was changed to read as follows in the Second Edition of the JLS:
The members of an interface are:

• Those members declared in the interface.


• Those members inherited form direct superinterfaces.
• If an interface has no direct superinterfaces, then the inter-
face implicitly declares a public abstract member
method m with signature s, return type r, and throws

14. Gosling et al., §8.4.7, “Overloading.”


15. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
(Reading: Addison-Wesley, 1996), §9.2, “Interface Members.” (Do not update.)

362 JAVA RULES


clause t corresponding to each public instance method m
with signature s, return type r and throws clause t
declared in Object, unless a method with the same signa-
ture, same return type, and a compatible throws clause is
explicitly declared by the interface.16
The first two bulleted items correspond to the original JLS, but the third item is
something new. Although this appears to be a very significant change in the Java
programming language, it is actually only a change in the specification. An inter-
face type could always be used to invoke the public methods in the Object
class. This only makes sense because if the value of an interface type variable is
non-null, then the variable references an object on the heap. That object nec-
essarily implements all of the public methods in the Object class, so why
force programmers to cast such an interface type reference to a class type in
order to invoke one of those methods? If the protected clone() method is
declared in an interface type, it too can be invoked. However, doing so requires
that classes implementing the interface have a public clone() method. The
same is true of the protected finalize() method.
The specification is wrong, however, to suggest that the final methods in
the Object class can be overridden. Neither subclasses nor interfaces can
declare a method that has the same signature as one of the final methods in
the Object class. Attempting to do so generates a compiler error. The JLS
needs to be updated to reflect this reality.
Does this mean that interfaces extend the Object class? The JLS says No.
The following quote is from the original JLS:
There is no analogue of the class Object for interfaces; that is, while
every class is an extension of class Object, there is no single inter-
face of which all interfaces are extensions.17

This paragraph was not substantially changed in the Second Edition of the JLS:
While every class is an extension of class Object, there is no single
interface of which all interfaces are extensions.18

16. Gosling et al., §9.2, “Interface Members.”


17. Gosling et al., §9.1.3, “Superinterfaces.” (Do not update.)
18. Gosling et al., §9.1.2, “Superinterfaces and Subinterfaces.”

HIDING AND INHERITANCE 363


This is an exercise in semantics, however. The fact of the matter is that in the
javac compiler19 interfaces do extend the Object class. As stated in the
evaluation of Bug Id 4479715:
The root of the problem is that javac represents an interface using the
same representation that it will put it into the class file; that is, an
interface extends Object. That doesn't agree with the specification
in 9.2, which says that the public members of Object are inserted
directly into the interface but the interface doesn't extend Object.20
[emphasis added]

The following is a similar comment from the closely related Bug Id 4526026.
javac implements the rules using inheritance instead of by explic-
itly inserting the members as per JLS 2 9.2 bullet 3. This subtle and
generally innocuous bug is the only known symptom (well, see also
4479715) of this compiler shortcut. Implementing the JLS2 directly
would require making special cases in many places in the compiler for
very little benefit. We are more likely to fix this by filtering out protected
members that were inherited into an interface.21 [emphasis added]

This is a problem because there are two protected methods in the Object
class: clone() and finalize(). Neither of these should be inherited by
interfaces. Here is an example from Bug Id 4479715:

interface I {
int clone();
}

This should compile (even though the result type is int instead of Object)
because clone() is a protected method. Prior to the 1.4.1 release, how-
ever, it generated the following compiler error:

19. According to a comment made in one of the bugs under discussion this is also true of the
jikes compiler, which would be very surprising because historically the jikes compiler team at
IBM has been a real stickler for implementing the JLS as written.
20. Evaluation of Bug Id 4479715. Bug Id 4526026 covers the same issue for the finalize()
method. Both of these bug reports along with a number of others have been consolidated into Bug
Id 4644627, “interfaces extend Object?” (though this is not immediately obvious in the case of Bug
Id 4479715), which is now the primary Bug Id for this issue.
21. Evaluation of Bug Id 4526026.

364 JAVA RULES


Test.java:2: clone() in Interface cannot override clone() in
java.lang.Object; attempting to use incompatible return type
found : int
required: java.lang.Object
int clone();
^
1 error

As is often the case with examples that break compilers, this declaration is
“completely useless”22 (because any attempt to implement this interface will
generate a similar compiler error). Nevertheless, as Gilad Bracha says in the
same bug report, an interface should have “no knowledge of the protected
members of Object, so there is no constraint on the clone method in an inter-
face.”22 This problem was solved by “filtering out protected members that were
inherited into an interface.”23 As of the 1.4.1 release, this example compiles but
the cat is out of this bag; interfaces do extend the Object class.
If the protected clone() and finalize() are filtered out by the
compiler, why does the following program generate an access control compiler
error (in the 1.4.1 release) instead cannot resolve symbol?

import java.util.*;
class Test {
public static void main(String[] args) {
Set set = new HashSet();
set.clone(); //clone() is not inherited by the Set interface
}
}

Attempting to compile this program generates the following compiler error:


Test.java:5: clone() has protected access in java.lang.Object
set.clone();
^
1 error

22. This is excerpted from an email Gilad Bracha sent to Eric Blake, and that is quoted in the bug
report. I do not know Eric Blake, but he submitted this bug report and many others apparently in
jikes-like effort to implement a Java compiler that is as true as possible to the specification.
23. Evaluation of Bug Id 4526026.

HIDING AND INHERITANCE 365


This is a bug. As stated in 1.9 Qualifying Type versus Compile-Time Declaration,
I believe this is why Bug Id 4644627, “interfaces extend Object?” is still marked
“In progress, bug.”
The public methods in the Object class that interfaces “implicitly
declare”16 define an interface that if explicitly declared would look like this:

public interface Object {


Class getClass();
int hashCode();
boolean equals(Object obj);
String toString();
void notify();
void notifyAll();
void wait(long timeout) throws InterruptedException;
void wait(long timeout, int nanos) throws InterruptedException;
void wait() throws InterruptedException;
}

Interfaces that override one of the three non-final methods in bold are subject
to the compiler-enforced method contract. It makes no sense for an interface to
override any of these methods (except perhaps to modify the interface con-
tract), however, because classes inherit their default implementations from the
Object class. The methods inherited from the Object class would override
and implement any method with the same signature declared in an interface. For
example,

class Test {
public static void main(String[] args) {
AnObject object = new AnObject() { };
}
}
interface AnObject {
int hashCode();
boolean equals(Object obj);
String toString();
}

This compiles, showing that all three of the abstract interface methods are
implemented by an anonymous class that declares no members.

366 JAVA RULES


3.8 Inheriting Members With The Same Name
When inheriting members with the same name it is important to differentiate
between inheriting the same entity more than once and inheriting different enti-
ties with the same name. The former is referred to as reinheritance and is dis-
cussed in the following subsection. In the case of methods, there is a third
possibility. Non-abstract methods implement abstract methods declared
in either a superclass or superinterface. Are these methods the same or differ-
ent? The answer is that they are the same. This subject is discussed in the last of
the following three subsections.

3.8.1 Re-Inheritance
Because of what is described as “(multiple) interface inheritance”24 in the JLS, it
is possible for a class or interface to inherit the same interface member from
more than one interface. This is referred to as re-inheritance. References to a
re-inherited field, method, or member type are never ambiguous. The fol-
lowing example illustrates one of a practically unlimited number of scenarios that
result in the “re-inheritance” of interface members.

interface SuperInterface {
String ROME = "re-inherited member"; //all roads lead to Rome
}
interface SubInterface extends SuperInterface { }
class Superclass implements SuperInterface { }
class Subclass extends Superclass implements SubInterface { }
class Test extends Subclass implements SubInterface {
public static void main(String[] args) {
System.out.println(ROME);
}
}

Executing the Test program prints re-inherited member. In this exam-


ple, the member SuperInterface.ROME is said to be re-inherited
through several paths. The simple name ROME is therefore not ambiguous.

24. Gosling et al, introduction to Chapter 9, “Interfaces.”

HIDING AND INHERITANCE 367


3.1
NOTE The following section discusses ambiguous field and member type
names. In both cases the names are ambiguous because of inheritance.
Type names can also be ambiguous because of type-import-on-demand
declarations. See 2.2.2 Disambiguating Type Names for a discussion.

3.8.2 Ambiguous Names Related to Inheritance


Classes can inherit more than one field with the same simple name. There are
two possibilities to consider:
• Conflicting Interfaces: Two or more interfaces have interface constants
with the same name. This can happen in an interface hierarchy even before
the subinterface is implemented by a class. An interface with ambiguous
field names will compile so long as the simple name of the field is not used
in a variable initializer for one of the interface constants (which is not very
likely).
• Conflicting Superclass and Superinterface: A superclass field has the
same name as one or more superinterface constants. Since Java identifiers
are case sensitive and the Java naming convention for constants is to use
all upper case letters, the implication here is that the field declared in the
superclass is also a constant.
In both cases, the simple name of the field is ambiguous and must be qualified.
For example,

class Superclass { static final boolean DEBUG = true; }


interface Interface { boolean DEBUG = false; }

class Subclass extends Superclass implements Interface {


void method() {
if (DEBUG)
System.out.println("ambiguous inlined constants");
}
}

Attempting to compile Subclass generates the following compiler error:

Test.java:6: reference to DEBUG is ambiguous, both variable DEBUG

368 JAVA RULES


in Superclass and variable DEBUG in Interface match
if (DEBUG)
^
1 error

Both DEBUG fields are inherited and neither shadows the other (because they
have the same scope). Qualified names must be used to reference either field in
the subclass. Another option is to use the super.fieldName general form to
access the superclass field.
The names of inherited member types can also be ambiguous. This is com-
pletely analogous to ambiguous field names. The difference is that inheriting two
or more member types with the same name is a lot less likely to occur. Never-
theless, here is an example:

class Superclass {
class Widget { }
}
interface Superinterface {
class Widget { }
}

class Test extends Superclass implements Superinterface {


public static void main(String[] args) {
System.out.println(Widget.class);
}
}

Attempting to compile this program generates the following compiler error:

Test.java:10: reference to Widget is ambiguous, both class


Superclass.Widget in Superclass and class Superinterface.Widget
in Superinterface match
System.out.println(Widget.class);
^
1 error

It does not matter that one of the inherited member types (the one declared in
Superinterface) is a nested top-level class and the other is an inner mem-
ber class. Nested type ambiguity (like that of fields) is based solely on the
type name. None of the modifiers are taken into consideration.

HIDING AND INHERITANCE 369


3.8.3 Inheriting Methods With the Same Signature
Are methods with the same signature the same method? The answer is an
unqualified Yes. Why then does 8.4.6.4 Inheriting Methods with the Same Signa-
ture in the JLS suggest otherwise? It reads in part:
It is possible for a class to inherit more than one method with the same
signature. Such a situation does not in itself cause a compile-time
error. There are then two possible cases:

• If one of the inherited methods is not abstract, then there


are two subcases:
° If the method that is not abstract is static, a
compile-time error occurs.
° Otherwise, the method that is not abstract is con-
sidered to override, and therefore to implement, all
the other methods on behalf of the class that inherits
it. A compile-time error occurs if, comparing the
method that is not abstract with each of the other
of the inherited methods, for any such pair, either
they have different return types or one has a return
type and the other is void. Moreover, a compile-time
error occurs if the inherited method that is not
abstract has a throws clause that conflicts with
that of any other of the inherited methods.
• If all the inherited methods are abstract, then the class is
necessarily an abstract class and is considered to inherit
all the abstract methods. A compile-time error occurs if,
for any two such inherited methods, either they have differ-
ent return types or one has a return type and the other is
void. (The throws clauses do not cause errors in this
case.) 25

If “one of the inherited methods is not abstract,”26 then this specification is


just plain wrong. The abstract methods would necessarily be interface meth-
ods. The rule here is that the interface methods are overridden and implemented

25. Gosling et al., §8.4.6.4, “Inheriting Methods with the Same Signature.”
26. Ibid.

370 JAVA RULES


by the non-abstract (instance) method. The interface methods are not in
fact inherited.
The more interesting case is the second bulleted item involving multiple
abstract methods with the same signature. The above specification is at
odds with 15.12.2.2 Choose the Most Specific Method, to which the Second Edi-
tion of the JLS added the following.
If all the maximally specific methods have the same signature, then:

• If one of the maximally specific methods is not declared


abstract, it is the most specific method.
• Otherwise, all the maximally specific methods are necessar-
ily declared abstract. The most specific method is
chosen arbitrarily among the maximally specific meth-
ods. However, the most specific method is considered to
throw a checked exception if and only if that exception is
declared in the throws clauses of each of the maximally
specific methods. 27 [emphasis added]
In other words, only one of the abstract methods is inherited, which one
does not really matter and is “arbitrarily” chosen. Methods with the same signa-
ture are therefore never ambiguous; they are the same method.
This addition to 15.12.2.2 Choose the Most Specific Method28 in the Second
Edition (and I presume the corresponding change in the language as well) was at

27. Gosling et al., §15.12.2.2, “Choose the Most Specific Method.” (Do not update.)
28. I strongly disagree with the JLS in that the issue of methods with the same signature should not
be addressed in 15.12.2.2 Choose the Most Specific Method which is about overloaded method
matching. Methods with the same signature are no overloaded, they are the same method.
That the JLS is inconsistent as a result can be seen in the above specifications. 8.4.6.4 Inheriting
Methods with the Same Signature in the JLS clearly says that all of the abstract methods are inher-
ited, whereas the addition to 15.12.2.2 Choose the Most Specific Method says that one of them is
“arbitrarily” chosen. Which is it? The answer is clearly that latter, but the JLS could do even better.
What does it mean to say that one of the abstract methods is “arbitrarily chosen”? The choice does
not affect the qualifying type of the method invocation. That is always going to be K in the Perera
example above; so what exactly does it mean to “arbitrarily” choose between two abstract
methods that have the same signature? Whatever the answer, this discussion should be moved to
8.4.6.4 Inheriting Methods with the Same Signature in the JLS. There is yet another problem with
this addition to 15.12.2.2 Choose the Most Specific Method. The discussion of throws clause is a
flawed reiteration of the same discussion in 8.4.4 Method Throws of the JLS. Including it here at
least suggests that it is somehow an exception to the rule. It is not.

HIDING AND INHERITANCE 371


the insistence of Roly Perera of the Java Spec Report. When I discovered this, I
asked him for an example. He replied in part:
...there are legal method invocations where there are two or more max-
imally specific methods. The First Edition (last paragraph of 15.11.2.2)
always required a compile-time error in this situation, disallowing:

interface I {
void f () throws X;
}

interface J throws X, Y{
void f ();
}

interface K extends I, J {
}

class Test {
public static void main (String[] args) {
K k = null;
k.f(); // ambiguous
}
}

However it is clear that in this case the method invocation is not ambig-
uous, since in an important sense I.f() and J.f() represent the
“same” (dynamically selected) method.29

Surprisingly, prior to the 1.2 release examples such as this would not compile.
Only methods with the same name but different signatures (i.e., overloaded
methods) can be ambiguous. However, this subject is not discussed until 5.8
Overloaded Method Matching.
That methods with the same signature are considered to be the same
method is further supported by the fact that they are subject to the compiler-
enforced method contract as discussed in 1.11.1 The Compiler-Enforced
Method Contract: all of the methods must have the same result type; a super-

29. Roly Perera, quoted with permission from a personal email with the subject line of “JLS2
15.12.2.2 addition” and dated June 6, 2003. Note that the throws clauses in f() are not rele-
vant to the immediate discussion.

372 JAVA RULES


class method that implements one or more interface methods must be declared
public; and there are special considerations for the throws clause dis-
cussed in 1.11.1.1 throws Clause Conflicts in abstract Methods.
What of semantically different methods with the same signature? By semanti-
cally different I mean different API docs. There are two cases to consider:
• A Class Hierarchy: A class inherits more than one abstract method
with the same signature. At most one of the abstract methods can be
inherited from what would necessarily be an abstract superclass. The
other abstract methods are inherited from superinterfaces. The rule
here is that any method declaration further down in the class hierarchy
that implements one of the abstract methods, implements all of them.
• An Interface Hierarchy: Likewise, a subinterface can inherit more than one
abstract method with the same signature from different superinterfaces.
This is not an example or re-inheritance because the methods are necessar-
ily declared in different interfaces. In a class that implements the subinter-
face, the same rule applies: any method declaration that implements one
of the abstract methods, implements all of them.
The JLS could do more to address the issue that if these are semantically differ-
ent methods that the class can provide only one implementation. After all, it
does take the trouble to address the less difficult problem of re-inheritance.
Bug Id 4364796 is interesting in this regard. The submitter assumes that
methods with the same signature that are declared in different interfaces are dif-
ferent methods. Is he (or she) wrong? That depends on the API docs. Unless the
methods are inherited from a common superinterface (which is referred to as
re-inheritance and is discussed in 3.8.1 Re-Inheritance), there is a very good
chance that they have different semantics (read different API docs). If that is the
case, then they are in fact different methods regardless of the fact that they
have the same method signatures. This amounts to a serious API design prob-
lem because a class can only provide one implementation of a given
method signature. At a bare minimum a change to one of the two method sig-
natures is required, but the problem may be much larger than that.

HIDING AND INHERITANCE 373


3.9 Designing Extensible Classes
It is now generally accepted that public classes should be declared final if
not carefully designed to be extended. I refer to this a capping a class hierar-
chy. The advantages of capping a class hierarchy are discussed in the next sec-
tion.
The Java programming language is really the first object-oriented program-
ming language to be so widely used as to raise the general level of awareness of
some of the more subtle pitfalls in object-oriented programming. One of the
most important of these lessons has been the following.

Subclasses are partially implemented by their superclasses and


depend on those superclass implementations not to change.

For example, if a superclass implementation is changed so that it no longer


requires a protected field, you cannot just delete that field. Doing so would
break subclasses. This is more generally true of any protected member.
Hence Bloch said the following in a BIll Venner interview.
As far as protected data in general, it's a necessary evil. It should be
kept to a minimum. Most protected data and protected methods
amount to committing to an implementation detail. A protected field is
an implementation detail that you are making visible to subclasses.
Even a protected method is a piece of internal structure that you are
making visible to subclasses.

The reason you make it visible is that it's often necessary in order to
allow subclasses to do their job, or to do it efficiently. But once you've
done it, you're committed to it. It is now something that you are not
allowed to change, even if you later find a more efficient implementa-
tion that no longer involves the use of a particular field or method.

So all other things being equal, you shouldn't have any protected mem-
bers at all. But that said, if you have too few, then your class may not
be usable as a super class, or at least not as an efficient super class.
Often you find out after the fact. My philosophy is to have as few pro-
tected members as possible when you first write the class. Then try to

374 JAVA RULES


subclass it. You may find out that without a particular protected
method, all subclasses will have to do some bad thing.30

This undesirable relationship between subclasses and superclasses is usually


expressed as “implementation dependencies break encapsulation” or less com-
monly as “inheritance breaks encapsulation.”
The UML defines the term dependency as follows.
dependency

A relationship between two modeling elements, in which a change to


one modeling element (the independent element) will affect the other
modeling element (the dependent element).31

Interestingly, the JLS also defines dependency:


A class C directly depends on a type T if T is mentioned in the
extends or implements clause of C either as a superclass or
superinterface, or as a qualifier within a superclass or superinterface
name. A class C depends on a reference type T if any of the following
conditions hold:

• C directly depends on T.
• C directly depends on an interface I that depends on T.
• C directly depends on a class D that depends on T (using this
definition recursively).
It is a compile-time error if a class depends on itself.32

Although the UML definition is much broader and the JLS definition is really
directed at what are best described as “compiler dependencies,” both defini-
tions are applicable to this discussion.
The definition of implementation dependencies is actually much more
precise than either of the above definitions. The protected fields and meth-
ods in a superclass are only the most obvious implementation dependencies.
Those fields and methods are by definition part of the superclass implementation

30. Joshua Bloch in an interview with Bill Venner entitled “A Conversation with Josh Bloch” (First Pub-
lished in JavaWorld, January 4, 2002), on the artima.com Web site (Artima Software, Inc.),
www.artima.com/intv/blochP.html.
31. UML Semantics, Glossary.
32. Gosling et al., §8.1.3, “Superclasses and Subclasses.”

HIDING AND INHERITANCE 375


(versus public fields and methods, which are part of the interface). Equally
important, however, are overridable methods, which in this context is defined
as any public or protected instance method in a superclass. Default
access methods are not an issue because they cannot be invoked outside of the
package in which they are declared. Here it is important to differentiate invoking
a protected method versus overriding one. Merely invoking a protected
method is an implementation dependency, but only in the sense that you depend
on the superclass method to do the same thing from one release to the next. In
that sense, the implementation dependency is not any different than what a cli-
ent programmer expects from the public interface. It is just one more method
that the class has to support from one release to the next.
Overriding a public or protected method, however, requires a detailed
knowledge of how the method is implemented.33 This is a much more subtle
implementation dependency. There is a very good example of this in the Col-
lections Framework. A subclass programmer who overrides any of the optional
operations named add, remove, or put must increment modCount in order
for fail-fast iterators to work properly. Likewise, implementing any of the optional
operations for an iterator, or implementing an iterator from scratch, requires the
subclass programmer to compare the modCount against the expected-
ModCount to make sure the backing container has not been structurally modi-
fied. These implementation details are irrelevant to client programmers. Never-
theless, if they are not documented in the API docs, subclass programmers
would have no idea why their fail-fast iterators do not work.

Thus superclass programmers are obligated to document the rele-


vant details of how an overridable method is implemented.

33. I must acknowledge Joshua Bloch as the source for this emphasis on documenting overridable
methods when designing a class to be extended. See “Item 15: Design and document for inherit-
ance or else prohibit it” in his book Effective Java (which is having a profound influence on the Java
programming community at large). He did not, however, originate the idea of designing a class to
be extended. That can be traced back to the First Edition of The Java Programming Language in
a section named “Designing a class to be extended.”

376 JAVA RULES


Such documentation is quite common in extensible classes. It is included in the
API documentation along with the interface specification for client programmers
and is easily recognizable because the word implementation usually appears at
the start of the first sentence in the paragraph. The following example is from
the iterator() method in the AbstractList class.
This implementation can be made to throw runtime exceptions in the
face of concurrent modification, as described in the specification for
the (protected) modCount field.

Whenever the API docs start talking about a method implementation, take a
moment to confirm for yourself that this is an overridable method in an extensi-
ble class. Then you know what you are reading is actually a note to subclass pro-
grammers and not part of the API specification. Bloch rightly suggests that
API documentation generator tools such as javadoc should be modified
at the earliest possible date to reflect this reality. Classes have three inter-
faces to maintain: the API specification, the so-called protected interface for
subclass programmers, and the serialized form. Why is the protected inter-
face being treated as if it were a second-class citizen? Just as there are separate
pages of automatically generated documentation for the @serial tag, there
should be a separate pages for automatically generated documentation directed
at subclass programmers using the @subclass tag.
Designing an extensible class therefore means both deciding which mem-
bers should be declared protected and documenting the implementation of
overridable methods. Failure to do either will make it difficult or impossible to
extend the class (especially if subclass programmers do not have access to the
source code). The “design” of the protected interface is essentially an effort
to minimize implementation dependencies while at the same time supporting
subclasses. Superclass programmers commit to supporting both the
protected members and the documented implementation details of overrid-
able methods. That part of the implementation cannot be changed without
“breaking” subclasses declared in other packages. Doing so is comparable to
changing the interface contract on which client programmers depend. This is
precisely why extensible classes are much more difficult to code than final

HIDING AND INHERITANCE 377


classes. You have two different interfaces to design and maintain. Not to dis-
courage the fainthearted, but Serializable classes have a third interface—
the serializable form. The serializable form suffers from a similar problem of
being closely tied to the implementation of a class. Unlike the client interface,
the protected interface and serialized form are moving targets because the
implementation of a class is generally expected to evolve over time. The best
you can hope to achieve in designing the protected interface or serialized
form of a class in early releases is to minimize the damage you do in later
releases.
The remainder of this section is an in-depth, practical analysis of how
protected instance variables, constructors, and instance methods are used
(similar to the analysis of static fields in 3.2.1 static Fields in Volume 1).
Very few books attempt this, and I would appreciate any feedback from readers.
The emphasis is primarily on protected instance methods, so the discussion
of protected instance variables and constructors is sparse.
There is nothing remarkable about protected class variables, class meth-
ods, or member types. They are merely accessible from within the body of sub-
classes declared in other packages (which is what I call the basic rule of
protected access). While protected class methods are a rare bird,
protected class variables (almost always constants) and nested types are
common.
Instance variables are declared protected for one of two reasons. The
first and most common reason is that the instance variable is set in an overrid-
able method and therefore must be inherited by subclasses. For example,

import java.util.Date;
class Test {
private long date;
public void setDate(Date date) {
if (date == null)
this.date = new Date().getTime();
else
this.date = date.getTime(); //effectively a defensive copy
}
}
class Subclass extends Test {

378 JAVA RULES


public void setDate(Date date) {
if (date == null)
throw new NullPointerException();
this.date = date.getTime();
}
}

This example does not compile because date is not inherited by the subclass,
effectively making it impossible to override the superclass method. The date
field should be declared protected.
The other use of protected instance variables is the field equivalent of
what this section describes as superclass implementation hooks (a very spe-
cial use of protected methods discussed below). The protected instance
variable is declared and accessed in a superclass, but subclasses are responsi-
ble for assigning a value. For example, AbstractList in the java.util
package includes the following declaration.

protected transient int modCount = 0;

Nowhere in the AbstractList class is a value assigned to this instance vari-


able. However, the iterators in AbstractList access modCount to make
sure there have not been any structural modifications to the backing container.
In some cases, the protected field is set in both the superclass and sub-
classes. An example of this is the following time field from the Calendar
class.

protected long time;

The primary responsibility for setting this field rests with subclasses, but it is
also set by the setTimeInMillis(long millis) method in the
Calendar superclass in which it is declared.
A private instance variable and a protected set method are some-
times used instead of a protected instance variable. The subclass program-
mer can either invoke the superclass set method or override the same. Here is
an actual example from the PrintStream and PrintWriter classes:

private boolean trouble = false;

HIDING AND INHERITANCE 379


public boolean checkError() {

return trouble;
}

protected void setError() {


trouble = true;
}

I could understand this alternative if the set method included an argument


check or some additional processing, but the examples I have seen do nothing
more than assign the value passed to what would otherwise be a protected
instance variable. Note that the corresponding get method may be either
public or protected. In the absence of an argument check or some addi-
tional processing, I would argue against the use of a protected set method
because the instance variable is understood to be part of the subclass imple-
mentation, and should be regarded as such. On the other hand, if a subclass
only needs to read a superclass instance variable (i.e., there is no corresponding
set method), then I would argue in favor of a protected get method (and
even defensive copies) as a matter of encapsulation.
Should a protected get and set method be declared final? This is
an interesting question because I have noticed in classes in which there has evi-
dently been a lot of thought given to subclasses, there is a tendency to declare
protected get and set methods final. How is this any different than
declaring a public get and set methods final? There is no difference. If
public accessor and mutator methods are not usually declared final, why
declare protected accessor and mutator methods final? There is clearly
no consensus on the need to do so. In the absence of any clear need for sub-
classes to override the get or set method, this is largely a matter of style.
There is also an open question as to whether or not protected instance
variables should be declared final. Doing so prevents a subclass from inad-
vertently hiding them. On the other hand, the hidden superclass instance variable
is easily accessed using the super keyword. Because hiding is generally dis-
couraged (and therefore not to be expected), it is really not necessary to declare

380 JAVA RULES


protected instance variables final. However, this too must be considered
a matter of style.
In a public abstract class, the constructors are usually protected.
(A public constructor in an abstract class is an invitation to do the impos-
sible—instantiate an abstract class.) Even if the class has only a default con-
structor, it is often explicitly declared so that the protected access modifier
can be used. Other than this one clearly identifiable use of protected con-
structors, protected constructors are not that common. They can only be
invoked from within the body of a subclass constructor. More often than not sub-
classes invoke the same public superclass constructors as do client pro-
grammers. See also the discussion of protected constructors in 2.8.3
Members More Accessible Than Their Class Type.
I have identified at least four distinct uses of protected instance meth-
ods. While reading the first two bulleted items, note that there is a significant dif-
ference between final and non-final protected instance methods:
• What I Describe As The “Normal” Use of protected Methods: These
are blocks of code that the superclass programmer has determined should
be reused as is in overriding implementations of the same method in sub-
classes. The code is therefore moved to a separate protected final
instance method that can be invoked in both the superclass and subclasses.
Otherwise you force subclass programmers either to retype the same code
(assuming he or she has access to your source code) or to re-invent the
wheel if they do not.
• Superclass Implementation Hooks: These are protected instance
methods that must be overridden in at least some subclasses. Superclass
implementation hooks are therefore easily differentiated from the “normal”
use of protected methods because they are always non-final. In their
purist form, superclass implementation hooks are protected
abstract methods. (In fact, protected abstract methods are
always superclass implementation hooks.) Instead of being declared
abstract, however, they may have an empty method body or a default
implementation that must be overridden by at least some subclasses.
Superclass implementation hooks are discussed at length in the remainder
of this section.

HIDING AND INHERITANCE 381


• The Serialization Mechanism: In Seriealizable classes designed
to be extended, methods such as readResolve() invoked during serial-
ization are declared protected.
• The Compiler-Enforced Method Contract: This applies to all methods
that are declared protected so that they can be overridden in sub-
classes. The protected methods in an extensible superclass are part of
the implementation, not the interface. This is significant because subclass
programmers do not generally override them with public methods.
Default access is less accessible than protected, so subclasses have no
choice but to declare the overriding method protected (because of the
compiler-enforced method contract). The protected clone() or
finalize() methods in the Object class are a special case. They are
sometimes declared public, especially the clone() method.
If not in fact complete, I think this list covers the overwhelming majority of
protected method uses. I will now discuss the concept of superclass imple-
mentation hooks in detail.
As first stated in 1.6.1 abstract Methods, a protected abstract
method is at first glance an inherent contradiction. Subclasses inherit either
interface or implementation. If the only reason to declare an instance method
protected is so that it is inherited, what does the subclass inherit? It does not

Superclass implementation hooks are a very special device in object-


oriented programming. They are used by superclasses to interface
with subclasses declared in other packages.

inherit interface because interface is defined in terms of public fields and


methods. Nor does it inherit implementation because abstract methods are
not implemented. The answer is that the subclass inherits nothing but the obli-
gation to implement the abstract method. This is a special programming
technique referred to above as superclass implementation hooks (or hooks
for short). Here is one possible implementation strategy:

class Test {
public static void main(String[] args) {
new Subclass().doSomething();

382 JAVA RULES


}
}
abstract class Superclass {
public void doSomething() {

hook();

}
protected abstract void hook();
}
class Subclass extends Superclass {
protected void hook() {
System.out.println("Peek-a-boo");
}
}

Superclass implementation hooks can even be invoked from private super-


class methods. Here is an example from the abstract Calendar class in
the java.util package:

public final Date getTime() {


return new Date( getTimeInMillis() );
}

protected long getTimeInMillis() {


if (!isTimeSet)
updateTime();
return time;
}

private void updateTime() {


computeTime();
if (isLenient() || !areAllFieldsSet)
areFieldsSet = false;
isTimeSet = true;
}

protected abstract void computeTime();

Note that computeTime() is one of the most important methods in the entire
Calendar class hierarchy. It is responsible for taking fields such as MONTH,
DATE, and YEAR and converting them into what is essentially a timestamp.

HIDING AND INHERITANCE 383


Lately there has been a new naming convention suggested for superclass
implementation hooks. The methods names begin with imple (for implementa-
tion), such as the implCloseChannel() method in the Abstract-
InterruptibleChannel class of the java.nio.channels.spi pack-
age. I am not particularly fond of this naming convention, but it looks like
abstact class names beginning with Abstract (which I think Bloch started
in the Collections Framework) and superclass implementation hooks beginning
with imple and going to be closely related naming conventions. The close()
method that invokes implCloseChannel() is interesting because it pro-
vides synchronization for the hook. As stated in the API docs:
This class performs the synchronization required to implement the
Channel specification. Implementations of the implClose-
Channel method need not synchronize against other threads that
might be attempting to close the channel.

This is accomplished simply by invoking the superclass implementation hook in a


synchronized method or block. For example,

public final void close() throws IOException {


synchronized (closeLock) {
if (!ChannelOpen)
return;
ChannelOpen = false;
implCloseChannel(); //synchronized hook
}
}

This is a slightly modified version of the implCloseChannel() method in


the AbstractInterruptibleChannel. Notice also the close()
method is final. Subclasses can only override the hook.
Another much more complicated example of this programming technique
can be found in the AbtractList class. The sublist() method in that
class instantiates a package-private AbtractList subclass named Sub-
List. Implementing SubList, in Bloch’s own words, is “not an easy task.”34

34. Joshua Bloch, Effective Java Programming Language Guide, (Boston: Addison-Wesley,
2001), “Item 15: Design and document for inheritance or else prohibit it.”

384 JAVA RULES


Indeed, AbstractList subclasses such as ArrayList do not even over-
ride the sublist() method. They use what Bloch describes as the
“subList mechanism”35 as is. An important part of that mechanism is the abil-
ity to invoke clear() on a sublist. Doing so requires the use of a superclass
implementation hook in the AbstractList superclass, the name of which is
removeRange(int fromIndex, int toIndex). It is invoked as follows.

public void clear() {


removeRange(0, size());
}

The removeRange(int fromIndex, int toIndex) method uses a


default implementation instead of being declared abstract. It is nevertheless
clearly a superclass implementation hook because the default implementation
runs in quadratic time for random access lists such as ArrayList (that imple-
ment the RandomAccess tagging interface). This extremely poor performance
means that removeRange(int fromIndex, int toIndex) is essen-
tially abstract for those subclasses. It must be re-implemented in order to
achieve an acceptable level of performance.
Why not just override the clear() method instead of using a protected
hook? This is why removeRange(int fromIndex, int toIndex) is
such an interesting example of a superclass implementation hook. The
SubList class must be able to invoke the clear() as if it were
clear(int fromIndex, int toIndex) precisely because it is only a
sublist. Thus Bloch killed two birds with one stone by forcing subclasses to over-
ride a range method. This is not, however, an example of how protected

35. Ibid.

HIDING AND INHERITANCE 385


instance methods are normally used (as suggested in Effective Java). Even
Bloch refers to this use of protected instance methods as a “hook.”36

Extracting reusable code from overridable methods and placing it in


protected final method that is inherited separately from the
overridable method is what I consider to be the normal use of
protected instance methods.

Superclass implementation hooks should be regarded as a very special use of


protected instance methods. As stated above, they make it possible for
superclasses (which generally have no knowledge of subclasses) to interface
with subclasses. There is a no man’s land between protected final meth-
ods (the “normal” use of protected instance methods) and superclass imple-
mentation hooks. One curiosity is protected instance methods not invoked in
the class in which they are declared. This is not at all unusual for protected
get and set methods, but otherwise most protected methods are invoked
in the declaring class. If you run across such a method, my guess is that it is
nothing more than an elaborate accessor method.
I want to close this section on designing extensible classes with a few mis-
cellaneous notes on the use of access modifiers. It is important to understand
why the discussion of implementation dependencies included only public and
protected classes. Package-private superclasses can be updated at the
same time as their non-public subclasses. As stated in the JLS:
Changes in top-level class and interface types that are not public
and that are not a superclass or superinterface, respectively, of a
public type, affect only types within the package in which they are
declared. Such types may be deleted or otherwise changed, even if
incompatibilities are otherwise described here, provided that the
affected binaries of that package are updated together.

This is an established principle in binary compatibility, and is equally true of the


subclass dependencies discussed in this section.

36. Ibid.

386 JAVA RULES


Most extensible public classes are designed to be extended by sub-
classes in different packages. If that is the case, there is an open question as to
whether or not most, if not all, of the methods should be public,
protected, or private. (In other words, no package-private methods). This
is equally true of nested classes declared protected and therefore obviously
designed to be extended in other packages. Some “trustful” programmers will
use package-private methods is such a case, as if they were actually class-
private. While this borders on being a matter of style, I use private meth-
ods all the time. Doing so is self-documenting and consistent with the principle of
always erring on the side of encapsulation. This means the only default access
methods in an extensible public class would be special cases in which the
method is actually accessed by unrelated classes in the same package. In that
case, it is customary to explain the access.
In the rare case in which a public class is designed to be extended, but
only by subclasses in the same package, public constructors cannot be used.
They must be replaced by factory methods in order to prevent subclassing in dif-
ferent packages. The class will still have to declare at least one constructor
(because default constructors have public access). That constructor must
have either default or protected access. The choice of default versus
protected access is a moot point is such a class. I would use default access
because protected implies something that is not true; namely, that the class
is extended in other packages.

3.10 Capping a Class Hierarchy


I use the phrase capping a class hierarchy to describe the habitual practice
of declaring classes at the bottom of a class hierarchy final, unless of course
the class is designed to be extended. It should be noted that anonymous classes
are “implicitly final.”37 The concept of capping a class hierarchy therefore
finds support in the following language from the Inner Classes Specification:
A compiler may also change a class to be final if it can determine
that it has no subclasses, and that there is no way for subclasses to be

37. Gosling et al., The Java Language Specification, §15.9.5, “Anonymous Class Declarations.”

HIDING AND INHERITANCE 387


added later. This is possible when a private or block-local class has
no subclasses in its scope. 38

Any class that has a name can be declared final, but you should not “cap”
local classes because it is an obvious source code optimization that compilers
will do for you.
Capping class hierarchies costs nothing in terms of binary compati-
bility. This is a very important point to grasp. As stated in the JLS:
Changing a class that was declared final to no longer be declared
final does not break compatibility with pre-existing binaries.39
Bloch addressed this in a Bill Venner interview when he said:
My view is you can always add something, but you can't take it away.
Make it final. If somebody really needs to subclass it, they will call you.
Listen to their argument. Find out if it's legitimate. If it is, in the next
release you can take out the final. In terms of binary compatibility, it is
not something that you have to live with forever. You can take some-
thing that was final and make it non-final. If you had no public construc-
tors, you can add a public constructor. Or if you actually labeled the
class final, you can remove the access modifier. If you look at the
binary compatibility chapter of the JLS (Chapter 13), you will see that
you have not hurt binary compatibility by adding extendibility [sic] in a
later release.40

I was happy to have read this statement from a Java luminary after having writ-
ten this section on capping class hierarchies five years earlier.
The benefits of capping a class hierarchy are:
• Method Inlining: Methods in a final class are said to be implicitly
final . They cannot be overridden because the class cannot be extended.

38. John Rose, Inner Classes Specification (Mountain View: Sun Microsystems, 1997), “Can a
nested class be declared final, private, protected, or static?”
39. Gosling et al., §13.4.2, “final Classes.”
40. Joshua Bloch in an interview with Bill Venner entitled “A Conversation with Josh Bloch” on the
artima.com Web site (Artima Software, Inc.), www.artima.com/intv/blochP.html.

388 JAVA RULES


Therefore, the overhead of dynamic method lookup can be eliminated by
inlining methods.41
• More Efficient Type Checking: A final class cannot be subclassed.
That allows the compiler to perform more compile-time type checking
because the class of an object referenced by a variable that has a final
class type must be the same class. There is no possibility of a widening ref-
erence type conversion. Thus the compiler knows more and can use com-
pile-time type checking where a non-final class would require more costly
run-time “type checking.” This not only improves system performance, but
also reduces the possibility of throwing a ClassCastException or an
ArrayStoreException at runtime. This is particularly true of conver-
sions involving interface types. See the bottom of 5.6.3.2 Narrowing Refer-
ence Conversions for examples and a detailed discussion.
• Enhanced Security: Hackers42 are notorious for extending classes in order
to access a system. A final class cannot be extended, but the impor-
tance of this benefit has been marginalized by package sealing.
Capping a class hierarchy can also be used to defer the design of the
protected interface. As stated in Effective Java:
…what about ordinary concrete classes? Traditionally, they are neither
final nor designed and documented for subclassing, but this state of
affairs is dangerous. Each time a change is made in such a class, there
is a chance that client classes that extend the class will break. This is
not just a theoretical problem. It is not uncommon to receive subclass-
ing-related bug reports after modifying the internals of a nonfinal con-
crete class that was not designed and documented for inheritance.

The best solution to the problem is to prohibit subclassing in


classes that are not designed and documented to be safely sub-
classed. There are two ways to prohibit subclassing. The easier of the

41. In a Classic JVM, method inlining is a source code optimization that requires using the –O or -
O:interclass compiler options. Source code optimizations have proven to be problematic,
however. In a newer HotSpot JVM, method inlining is a bytecode optimization performed at run time.
Note that static and private methods can also be inlined for the same reason as final
methods (dynamic method lookup is not required).
42. Human language is in a constant state of flux. Some purists prefer cracker to hacker. Popular
usage, however, dictates that a hacker is a cracker. Furthermore, in some circles cracker has an
entirely different, pejorative meaning (as in “you honky cracker”).

HIDING AND INHERITANCE 389


two is to declare the class final. The alternative is to make all the con-
structors private or package-private and to add public static factories
in place of the constructors.

If capping a class hierarchy costs nothing, improves program performance, and


enhances system security, there is no reason not to declare the classes at the
bottom of a class hierarchy final.

390 JAVA RULES


Chapter 4

Expressions, Statements,
and Blocks

Chapter Contents
4.1 Introduction 392
4.2 Expressions 393
4.2.1 Primary Expressions 397
4.2.2 Expression Statements and Other Top-Level Expressions 402
4.3 Operator Expressions 405
4.3.1 Numeric Promotion 407
4.3.2 Operator Order of Precedence and Parenthesized Expressions 415
4.3.3 The Associative Property of Operators 428
4.3.4 Nondestructive Operators 430
4.4 Exceptions are Precise 431
4.5 The 38 Unary, Binary, and Ternary Operators 435
4.5.1 Increment and Decrement Operators -- and ++ 436
4.5.2 Negation Operators -, ~, and ! 438
4.5.3 The Elementary School Operators 444
4.5.4 Remainder Operator % 446
4.5.5 Boolean Logical Operators &&, ||, &, |, and ^ 450
4.5.6 Bitwise Operators &, |, ^, >>, >>>, and << 454
4.5.7 Ternary Conditional Operator ?: 465
4.5.8 The Simple and Compound Assignment Operators 470
4.6 The instanceof Type Comparison Operator 473
4.7 A Bitwise Primer 475
4.7.1 Bits 477
4.7.2 Converting Nybbles to Hexadecimal Digits 492
4.7.3 General-Purpose Integer to String Conversion Methods 498
4.7.4 Unsigned Bytes 501
4.7.5 Some Miscellaneous Uses of the Bitwise Operators 523

EXPRESSIONS, STATEMENTS, AND BLOCKS 391


4.8 Statements 526
4.8.1 Control-flow Statements 529
4.8.1.1 The switch Statement 538
4.8.1.2 The for Statement 546
4.8.1.3 The do and while Statements 554
4.8.2 Labeled Statements 554
4.8.3 Control-transfer Statements (a.k.a. Abrupt Completion) 560
4.8.3.1 The continue Statement 561
4.8.3.2 The break Statement 562
4.9 Blocks 566

4.1 Introduction
The bulk of the work done in a Java program is accomplished by evaluating
expressions. They are the workhorses of a programming language. All expres-
sions are either primary expressions or operator expressions. The section
on primary expressions in the JLS begins with the following paragraph:
Primary expressions include most of the simplest kinds of expressions,
from which all others are constructed: literals, class literals, field
accesses, method invocations, and array accesses. A parenthesized
expression is also treated syntactically as a primary expression. 1

“From which all others are constructed” simply means that operator expressions
are composed of a sequence of one or more operators and primary expres-
sions. This is true no matter how complex the operator expression.
As first discussed in 1.8 Operators in Volume 1, there are a total of 40 oper-
ators in the Java programming language:
1. cast operator
1. instanceof operator
38. unary, binary, and ternary operators
40. total

1. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java Language Specification, Sec-
ond Edition (Boston: Addison-Wesley, 2000), §15.7, “Primary Expressions.”

392 JAVA RULES


In addition, the + operator is overloaded for string concatenation. The cast oper-
ator is discussed in 5.7.4 The Cast Operator. All of the other operators are dis-
cussed in 4.5 The 38 Unary, Binary, and Ternary Operators.
Expressions never stand alone (except as top-level expressions). They are
used in statements. The most obvious difference between an expression and a
statement is that a semicolon is placed at the end of a statement, but not at the
end of an expression. Another difference is that expressions are said to be “eval-
uated,” whereas statements are said to be “executed.” This chapter discusses
all of the statements in the Java programming language. After expressions and
statements, there is a short section on blocks.

4.2 Expressions
The term expression is poorly defined, or left undefined, in most books about
computer programming languages. Those that do attempt a definition usually err
by defining only operator expressions involving unary, binary, and ternary opera-
tors. Primary expressions are excluded by such a definition. Primary expres-
sions include literals, local variables and parameters, the simple name of a field,
the this keyword, as well as the more complex primary expressions such as
field access expressions, method invocation expressions, and class instance
creation expressions.
What then is the essential meaning of the term expression? Is there one def-
inition that can describe everything from a local variable or parameter to a class
instance creation expression or a complex operator expression? How about “any
token or combination of tokens that represents a single value.” Is this an accept-
able definition of the term expression as used in computer languages? Well, No.
This definition does not work because invoking a method that has a result type
of void is also an expression—a “void expression” that denotes nothing.
This is the reason why so few books attempt to define the term expression.
No one definition can suffice because there are actually three very distinct kinds
of expressions. When an expression is evaluated, the result denotes one of the
following:

EXPRESSIONS, STATEMENTS, AND BLOCKS 393


• A value
• A variable
• Nothing
Expressions that denote a value (primitive numeric types, char, boolean, or
references to objects) are by far the most common kind of expression, and
include all operator expressions.
There are only two uses of an expression that denotes a variable.
• The left-hand operand of a simple or compound assignment expression
• A primitive numeric type variable in one of the following four unary operator
expressions:
postfix increment expression
postfix decrement expression
prefix increment expression
prefix decrement expression
These are sometimes referred to as lvalues (or location values).2 In either case, the
variable may be a local variable or parameter, the simple name of a field, a field
access expression, or an array access expression (i.e., any kind of variable). A
compiler error is generated if the variable is declared final. For example:

class Test {
public static void main(String[] args) {
final int x = 0;
x = 10;
x++;
}
}

Attempting to compile this program generates the following compiler errors:

Test.java:4: cannot assign a value to final variable x


x = 10;

2. There are lvalues and rvalues. A variable is said to have both an lvalue and an rvalue, whereas
a constant only has an rvalue. The lvalue is the location in which the value is stored. The rvalue (or
read value) is the value stored in that variable. This is a somewhat antiquated terminology that is not
used in either the JLS or the JVMS. Because it is primarily used in reference to assignment state-
ments, lvalue and rvalue are sometimes interpreted to mean the left and right-hand operands.

394 JAVA RULES


^
Test.java:5: cannot assign a value to final variable x
x++;
^
2 errors

It is interesting to note that because these are the only operator expressions
that include a primary expression that denotes a variable, they are also the only
way to change the value of a variable.
The third kind of expression is the most narrowly defined of all. Invoking a
method that has a result type of void is the only expression in the Java pro-
gramming language that denotes nothing. Such method invocation expressions
can only be invoked as a top-level expression. They are executed for their side
effects. The term top-level expression is defined in 4.2.2 Expression State-
ments and Other Top-Level Expressions.
Note that none of these definitions for the term expression include type
names or labels, neither of which are considered expressions. For example, the
type name in a cast operator or in the right-hand operand of an instanceof
operator denotes a type, not a value. Nor do the labels used in break and
continue statements denote a value. Having said this, it could be argued the
type names and labels are primary expressions that denote a type or label,
respectively. The defining characteristic of a primary expression is that
there are no operators. If some primary expressions denote a variable, why
cannot others denote a type or label? Such a redefinition of the term expres-
sion, however, would be inconsistent with 15.8 Primary Expressions in the JLS
As an lvalue must denote a non-final variable, an analysis of the other
uses of expressions uncover similar rules of which you should be aware. For
example, expressions used in control-flow statements must evaluate to the
boolean data type. The six general uses of expressions are summarized in
Table 4.1. This analysis does not include subexpressions or operands. There are
arguably other highly specialized uses of expressions:

EXPRESSIONS, STATEMENTS, AND BLOCKS 395


Table 4.1 Where Expressions are Used
General Use Description

b Variable initializers These are the initial values for variables. They differ only
c Argument expressions in that arguments provide the initial value for method or
constructors parameters.

d Control flow expressions All six of the control-flow statements use Boolean expres-
sions enclosed in parentheses. This group also includes
the Boolean expression in a conditional operator (?:).
Finally, if you understand how the compiler implements
assert statements, then the Boolean expressions
used in assertions must also be included in this group.

e Control transfer There are four control-transfer statements, but only two of
expressions them are included in this group. Those are the return
and throw control-transfer statements. As discussed
above, the labels in a break or continue
statements are not expressions. The commonality of this
group of expressions is that they are usually very simple.
The type of the expression in a return statement must
be assignment compatible with the result type. The type
of an expression in a throw statement must be
Throwable or a subclass thereof.

f Dimension and index Both dimension and index expressions are enclosed in
expressions brackets, and must evaluate to integral types other than
long. Dimension expressions are used in array creation
expressions; index expressions are used in array access
expressions. The type of these expressions is promoted
to int using unary numeric promotion.

g Top-level expressions Top-level expressions are discussed in 4.2.2 Expression


Statements and Other Top-Level Expressions.

• It could be argued that the expression in a synchronized statement is a


separate use of expressions.
• The left-hand operand in the instanceof operator could also be consid-
ered a separate use of expressions.
• The array reference expression in an array access expression is arguably a
separate use of expressions.

396 JAVA RULES


Such uses are better thought of as part of the syntax for a particular statement
or operator, however, than considered separately in a general discussion of
expressions.

4.2.1 Primary Expressions


As defined in 4.3 Operator Expressions, any expression that includes an opera-
tor is an operator expression. Conversely, any expression that does not
include an operator is by definition a primary expression. Note that the new
keyword is not an operator. Nor are any of the so-called membership opera-
tors[], “.”, and (params). These are separators, not operators. The following
is a complete list of primary expressions in the Java programming language:
• All Literals: This includes class literals
• Every Kind of Variable: Local variables and parameters, the simple name
of a field, field access expressions, array access expressions, and the
this keyword
• Method Invocation Expressions: This does not include invoking methods
with a result type of void
• Class Instance and Array Creation Expressions: In other words, any use
of the new keyword
This is nearly identical to the list at the bottom of Table 1.7 The Five General
Forms and Qualifying Types on page 196, except that only reference type liter-
als are used in the primary expression general form. In that section I suggest
that the reader memorize these primary expressions. This should not be difficult
to do.

All literals, variables, method invocation expressions, and any use of


the new keyword in a class instance or array creation expression is
a primary expression.

Note that this includes all of the expressions that have long names (consisting of
three or more words):

EXPRESSIONS, STATEMENTS, AND BLOCKS 397


• Field access expressions and array access expressions
• Method invocation expressions
• Class instance creation expressions and array creation expressions
I refer to these as the complex primary expressions. There are five of them.
They access fields, invoke methods, and create objects.
The list of primary expressions above differs from the list in the JLS only in
that it does not include parenthesized expressions. Parenthesized expressions
are best thought of as a programmer device for overriding normal operator pre-
cedence, and as such are discussed in 4.3.2 Operator Order of Precedence and
Parenthesized Expressions.
Most programmers do not think of variables as “expressions.” Nevertheless,
they are. Depending on the context in which a variable is used, the “expression”
denotes either the value stored in the variable (the rvalue) or the variable itself
(the lvalue). For example:

a = b + c;

The left-hand operand of this assignment expression denotes a variable. The


right-hand operand is an additive expression. Both of the addends are variables
that denote a value. If fact, if you think about the phrase “variable or expression”
as used in 5.2 The Type of a Variable or Expression versus the Class of an
Object, it is erroneous because a variable is an expression. Nevertheless, the
phrase “variable or expression” is common in both the JLS and this book. The
explanation is simply that variables are not generally thought of as expressions.
At one point the purist in me changed all “variable or expression” to “variable or
other expression” throughout this book, but that sometimes reads awkwardly. It
was retained in some places and removed in others (such as the section name
mentioned above).
As stated above, the defining characteristic of a primary expression is that
there are no operators. There is a more fundamental meaning of primary
expression, however. This use of the adjective primary means “not derived
from.” A few primary expressions have subexpressions. For example, an array
access expression has two subexpressions. In the array access expression
array[0], the variable name array is an array reference expression, and

398 JAVA RULES


[0] is an index expression, but the value of an array access expression is
not derived from either of these subexpressions in the sense that the value
of an operator expression is derived from its operands. Likewise, the value of a
method invocation or class instance creation expression is not derived from the
argument expression in an argument list. Primary expressions are thus original
sources of values.
One of three things happens to the value of a primary expression.
1. It is used as an argument in another primary expression (a method invoca-
tion or class instance creation expression)
2. It is used as an operand in an operator expression.
3. It is “quietly discarded.”
Discarding the value of a primary expression can happen when you invoke a
method or create an object in a top-level expression. For example,

class Test {
static int value = 5;
public static void main(String[] args) {
getValue();
new Test();
}
static int getValue() {
return value;
}
}

The value returned by the getValue() method or the reference to the newly
created Test object is said to be “quietly discarded.” Most of the time, how-
ever, the value of a primary expression is used as an argument or an oper-
and in some larger context.
Much like operator expressions, the complex primary expressions (the ones
with long names) have an order of evaluation. The term order of evaluation
refers to the order in which subexpressions are evaluated. Understanding the
order of evaluation is important for two reasons. If an expression has a side
effect, then it is particularly important for a programmer to know exactly when
the side effect occurs. The other reason is debugging. A programmer who does
not understand the order of evaluation will find it difficult to debug complex

EXPRESSIONS, STATEMENTS, AND BLOCKS 399


expressions. There are three general rules for evaluating complex primary
expressions:
• Subexpressions (including notably argument lists) are evaluated from left to
right. The one exception to this rule is the dimension expression(s) in an
array creation expression
• Subexpressions are always “fully evaluated”
• NullPointerException is thrown late. Otherwise, exceptions are
precise
The fact that exceptions are precise is not discussed until 4.4 Exceptions are
Precise. The rest of this section discusses the first two rules.
Most programmers know that argument lists are evaluated from left-to-right,
but this rule is almost universally true of all subexpressions. The following pro-
gram is a simple example of the left-to-right evaluation of an argument list:

class Test {
static char a = 'a', b = 'b';
public static void main(String[] args) {
print(a, b, b = 'c');
}
static void print(char a, char b, char c) {
System.out.println("" + a + b + c);
}
}

Executing this program prints abc because the assignment expression b = 'c'
is not evaluated until after the second argument expression is evaluated.
The fact that dimension expressions in an array creation expression are eval-
uated before an array is actually created is an exception to the general rule that
expressions are evaluated from left-to-right. The dimension expression(s) in an
array creation expression must be evaluated before the array is created because
the value of a dimension expression determines the length of the array to be cre-
ated. The order of evaluation for an array creation expression is as follows.

400 JAVA RULES


1. Dimension expressions are evaluated from left-to-right before the array is
created. If a dimension expression is negative, a NegativeArray-
SizeException is thrown.
2. The array is created on the heap. If there is not enough memory available to
create the array, an OutOfMemoryError is thrown.
For example,

class Test {
public static void main(String[] args) {
int[] array = new int[getArrayDimensions()];
}
static int getArrayDimensions() {
System.out.println("array dimension expressions are " +
"evaluated first");
return Integer.MAX_VALUE;
}
}

Executing this program prints:

array dimension expressions are evaluated first


Exception in thread "main" java.lang.OutOfMemoryError

The argument expressions in a comparable class instance creation expression


are evaluated after the object is created. Essentially the same point was made
in 1.3.3 Constructors by pointing out that you can use the this keyword in con-
structors. I also noted in the same chapter that the term constructor is some-
thing of a misnomer because they really do not construct anything. Constructors
are merely a mechanism for dynamically assigning values to instance variables
immediately after an object is created on the heap.
The term fully evaluated, or the expression “appears fully evaluated,” as
used in the JLS, simply means that any side effects are considered part of evalu-
ating an expression. For example,

class Test {
public static void main(String[] args) {
int x = 1;
System.out.println((x = 2) + x );
}
}

EXPRESSIONS, STATEMENTS, AND BLOCKS 401


The value of the expression x = 2 is two. Executing this program prints 4. In this
case, the significance of the fact that subexpressions are fully evaluated is that
two is assigned to x before that addition operation is performed. Otherwise, the
value of this expression would be three instead of four.

4.2.2 Expression Statements and Other Top-Level Expressions


Before top-level expressions can be discussed, it is necessary to define the
term expression statement. Most expressions are evaluated for their value,
which is then used as either an argument or operand in some larger context,
whereas statements are executed for their effect. If an expression has an effect,
it is called a side effect. The difference is most pronounced in methods. As the
name suggests, method invocation expressions are expressions, not state-
ments. They have a value (the return value). What a method does, however, is
considered a side effect. (Methods declared void are a special case discussed
below.) Table 4.2 lists all eight of the expressions that produce side effects. This

Table 4.2 Expressions that Produce Side Effects


Expression(s) Value versus Side Effect

Assignment expressions It is somewhat counterintuitive to think of an assignment


expression as having a value. The value of an assignment
expression is the value of the variable denoted in the left-
hand operand after the assignment. The side effect is the
assignment. The fact that an assignment expression has
a value can be seen in statements such as the following:

a = b = c;

The value of the assignment expression b = c is


assigned to variable a. The fact that the same value is
also assigned to the variable b is a side effect of
evaluating the expression b = c.

Method invocation expressions The value of a method invocation expression is the value
returned in the body of the method invoked. The side
effect of a method invocation expression is everything
else that the method does.

402 JAVA RULES


Table 4.2 Expressions that Produce Side Effects (Continued)
Expression(s) Value versus Side Effect

Class instance and array The value of a class instance creation expression or an
creation expressions array creation expression is a reference to an object. The
side effect is the creation of an object on the heap.
Additional side effects occur if the class has not already
been loaded, linked, and initialized (including the
dynamically created array classes).

Prefix increment expressions The value of an increment or decrement expression is the


Prefix decrement expressions value of the variable before a postfix increment or
Postfix increment expressions decrement expression is evaluated or after a prefix
Postfix decrement expressions increment or decrement expression is evaluated. The
actual increment or decrement of the variable denoted in
the expression is a side effect of these expressions.

is an important table because any expression that produces a side effect can be
followed by a semicolon and executed as a statement. When executed as state-
ments, these expressions are referred to as expression statements. Here are
some examples of expression statements:

a = b + c;
System.out.println("Goodbye Cruel C++ World");
i++;
new Main();
––array[i];

With the exception of System.out.println("Goodbye Cruel C++


World"), all of these expressions could be used as arguments or operands in
some other expression. For example:

x = Math.pow(2, a = b + c);

In this assignment expression, the value of a = b + c is used as an argument in


a method invocation expression. The fact that the value of b + c is assigned to
the variable a is a side effect of evaluating the argument expression.
The term top-level expression refers to an expression that produces a
side effect used in one of the following contexts:

EXPRESSIONS, STATEMENTS, AND BLOCKS 403


• As an expression statement
• In the ForInit or ForUpdate part of a for statement header
The JLS describes top-level expressions as “situation[s] where a value is not
needed”3 If a top-level expression has a value (and they all do except when invok-
ing methods declared void), the value of the expression is said to be “quietly
discarded.” Top-level expressions are like statements in that they are
executed only for their (side) effects only. Hence, only the expressions in
Table 4.2 (i.e., expressions that produce side effects) can be used as top-
level expressions.
Some Java books, ignorant of the definition of a top-level expression,
assert that methods declared void must be invoked in expression statements.
To show how easy it is to make this mistake, both editions of the JLS have
included the following:
An expression denotes nothing if and only if it is a method invocation
that invokes a method that does not return a value, that is, a method
declared void. Such an expression can be used only as an expression
statement, because every other context in which an expression can
appear requires the expression to denote something. 4

The JLS is wrong to say that “such an expression can be used only as an expres-
sion statement.” It is possible, though somewhat unusual, to invoke such a
method in the ForInit or ForUpdate part of a for statement header. For example,

import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("Test.java");
for (int c;(c = fr.read()) != -1;System.out.print((char)c))
continue;
}
}

3. Gosling et al., §15.11.3, “Compile-Time Step 3: Is the Chosen Method Appropriate?”


4. Gosling et al., §15.1, “Evaluation, Denotation, and Result.”

404 JAVA RULES


Executing this program prints the source code for Test.java to the console.
Note that the result type of the overloaded System.out.print method is
void.

4.3 Operator Expressions


Any use of the 38 unary, binary, and ternary operators discussed in this chapter
is by definition an operator expression. All operator expressions have subex-
pressions called operand expressions. Operator and operand expressions are
usually referred to as just operations and operands, respectively. In the JLS,
specific names such as “additive expression” or “shift expression” are used for
operator expressions. Most of the time you just take the operator name and
replace “operator” with “expression.” For example, the postfix increment opera-
tor is used in postfix increment expressions. These more specific expression
names, however, do not add anything to the discussion. I prefer to use the more
general operator expression or operation.
The very first thing you must look for in evaluating an operator expression is
the use of parentheses. Parentheses override the normal order of evaluation in
operator expressions. Otherwise, the following order of evaluation is largely a
factor of the operator order of precedence.
1. Evaluate Primary Expressions (and Note Side Effects): The order of eval-
uation for primary expressions is discussed in 4.2.1 Primary Expressions.
2. Evaluate Unary Operator Expressions: Perform all unary operations asso-
ciated with the primary expression except postfix increment and decrement
operations. They must be performed after the expression is evaluated.
3. Evaluate Parenthesized Expressions: If parentheses are used, evaluate
the parenthetical subexpressions first by following this entire procedure for
each parenthetical subexpression. Subexpressions within the parentheses
are evaluated from left-to-right. If there are parenthetical subexpressions
within parenthetical subexpressions, begin by evaluating the innermost par-
enthetical subexpressions first, ending with the outermost parenthetical
subexpressions.
4. Evaluate Left-Associative Binary Operator Expressions: These are all of
the binary operators except the right-associate simple and compound
assignment operators. For each of the ten left-associative precedence lev-

EXPRESSIONS, STATEMENTS, AND BLOCKS 405


els in Table 4.3 on page 416 scan the expression once from left-to-right per-
forming any operation in the expression at that level of precedence. It is
important to follow the operator order of precedence when doing so.
Both operands are fully evaluated before the binary operation is performed.
The left-hand operand is always evaluated before the right-hand operand.
The right-hand operand of the && and || operators may not be evaluated.
See 4.5.5 Boolean Logical Operators &&, ||, &, |, and ^ below for a dis-
cussion.
5. Evaluate Ternary Operator Expressions: Scan the expression from right-
to-left performing any conditional operations (the ?: operator). The rules for
executing the ternary conditional operator ?: are completely different from
those for binary operators, and are discussed in 4.5.7 Ternary Conditional
Operator ?:. Java books disagree about whether this operator is left- or
right-associative. The JLS says:
The conditional operator is syntactically right-associative (it
groups right-to-left), so that a?b:c?d:e?f:g means the
same as a?b:(c?d:(e?f:g)) .5

It should be clear from this quote that the conditional operator is right-asso-
ciative.
6. Perform Assignment Operations: These are all of the right-associative
binary operators. Scan the expression from right-to-left performing any
assignment operations. Compound assignment operators are executed as if
they were two separate operations with the result type of the first operation
cast to the type of the left-hand variable before the assignment operation is
performed. See 4.5.8 The Simple and Compound Assignment Operators
below for a discussion. Do not forget to increment or decrement the vari-
able in any postfix increment or decrement operations when you are
done manually evaluating the operator expression (presumably on a
blackboard).
There should be a single value left in the end. That is the value of the operator
expression. In the language of the JLS, every operator expression has a type
and a value. Often the value is referred to as the result. Thus it is natural to use

5. Gosling et al., §15.25, “Conditional Operator ?:.”

406 JAVA RULES


the term result type to refer to the type of operator expressions, not just
method invocation expressions.
The following subsections on numeric promotion, operator order of prece-
dence, parenthesized expressions, and the associative property of operators
apply to operator expressions in general. Specific operators are discussed in
detail in 4.5 The 38 Unary, Binary, and Ternary Operators.

4.3.1 Numeric Promotion


The boolean data type has “truth values” in the JLS. Truth values, however, do
not compute. Nor can they be written to a file or stored in a database. Inside of
a class file and on the operand stack of a JVM, the boolean data type is pro-
moted to an int (a computational type) in which zero is false and one is
true. (Boolean values are never stored as operands in a JVM instruction. They
are only ever CONSTANT_Integer values in the constant pool of a class file
or int values on the operand stack.) When serializing a boolean value (a stor-
age type), an unsigned byte is used to store the same integer values of zero and
one. For example,

class Test implements Serializable {


boolean b0 = false;
boolean b1 = true;
public static void main(String[] args) {
String fileName = "test.ser";
try {
ObjectOutputStream serialize = new ObjectOutputStream
(new FileOutputStream(fileName));
serialize.writeObject(new Test());
serialize.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
hexDump(fileName);
}
static void hexDump(String fileName) {
File file = new File(fileName);
if (!file.exists() || !file.isFile()) {
System.err.println(file + " does not exist");

EXPRESSIONS, STATEMENTS, AND BLOCKS 407


System.exit(1);
}
byte[] data = null;
try {
FileInputStream fis = new FileInputStream(file);
data = new byte[fis.available()];
fis.read(data);
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
char[] hexDigits = {'0','1','2','3','4','5','6','7',
'8','9','A','B','C','D','E','F'};
for(int i=0; i < data.length; i++) {
System.out.print(hexDigits[data[i] >>> 4 & 0xF]);
System.out.print(hexDigits[data[i] & 0xF]);
if (i < data.length - 1)
System.out.print(" ");
}
}
}

Executing this program prints the following hex dump of the test.ser file:

AC ED 00 05 73 72 00 04 54 65 73 74 52 F8 93 41 29 2E 78 A9 02 00
02 5A 00 02 62 30 5A 00 02 62 31 78 70 00 01

The last two bytes are the boolean values. As you can see, 00 is false and
01 is true. The hex dump also shows that boolean values are serialized as
bytes. Indeed, the interface contract for the readBoolean() method in the
DataInput interface begins with the following sentence.
Reads one input byte and returns true if that byte is nonzero, false
if that byte is zero.6

How a boolean value is stored in a database depends on the software vendor.


It is safe to assume, however, that most of them are using a bit in order to mini-
mize the amount of storage required to save boolean values. Thus, the
boolean data type has one of two truth values according to the JLS, is an int

6. API docs for the readBoolean() method in the java.io.DataInput interface.

408 JAVA RULES


in class files and on the operand stack of a JVM, is serialized as a byte, and is
most likely stored as a bit in databases. These different data formats are shown
in Figure 4.1. It is interesting to note that when allocating objects on a HotSpot

Figure 4.1 Computational and Storage Types

VM heap that the boolean data type is also a byte.7 Likewise, implementa-
tions are free to use either arrays of bytes or bits (referred to a packed
boolean arrays in the JVMS8) to implement a boolean[]. The bastore
machine instruction (used to store boolean and byte values on the operand
in arrays) includes the following documentation.
If the components of the array are of type boolean, the int value is
truncated to its low order bit then zero-extended to the storage size for
components of boolean arrays used by the implementation.

Thus for at least one machine instruction a boolean value is always a bit.
The boolean data type is the extreme case is this regard, but is useful in
introducing the concept of computational types. There are only four primitive
data types in the constant pool of a class file: int, long, float, and
double. These same data types are known as computational types in a

7. See Bug Id 4392283 which states that “Booleans are 1 byte fields instead of 4 bytes since the
object packing change was implemented.” Object packing is briefly discussed in a number of
HotSpot VM documents (for example, java.sun.com/products/hotspot/docs/
whitepaper/Java_HSpot_WP_v1.4_802_2.html).
8. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, Second Edition,
(Boston: Addison-Wesley, 1999), documentation for the newarray machine instruction.

EXPRESSIONS, STATEMENTS, AND BLOCKS 409


JVM.9 There are two things that assure that only computational types are written
to the constant pool of a class file.
• All numeric literals have one of the JVM computational types. Both character
and Boolean literals are written to class files as integer constants.
• The compiler “promotes” the byte, short, char, and boolean data
types to one of the computation types when used in operator expressions.
The promotion of these data types (including notably the char and boolean
data types) to one of the computational types is referred to as numeric promo-
tion.
The byte, short , char, and boolean data types are sometimes
referred to as storage types. The assumption is that when written to a class file
or stored in a database, that these narrower data types are “demoted” so-to-
speak back to their original data format so as not to waste storage space. The
term storage type can be misleading, however, when applied to the boolean
data type. As already noted, truth values cannot be stored in a database. This is
perhaps why the JVMS uses the term actual type instead storage type.
There are two reasons for promoting primitive data types. The most obvious
reason is that a common data type is required in order to evaluate operator
expressions, much like division operations require a least common denominator.
In fact, the JLS refers to the promoted type as a common type:
Numeric promotions are used to convert the operands of a numeric
operator to a common type so that an operation can be performed.10

The more fundamental reason, however, is to minimize the number of machine


instructions in the Java programming language. Many machine instructions are
type specific. Java Virtual Machine instructions are also known as bytecodes
because they are one byte in length. Bytecodes make for the smallest possible
class files. They are “designed to transport code efficiently,”11 but also limit the
JVM to 255 machine instructions. There simply are not enough of them to define

9. There are actually two other computational types in a JVM: reference and return-
Address.
10. Gosling et al., §5.6, “Numeric Promotions.”
11. James Gosling and Henry McGilton, The Java Language Environment: A White Paper (Mountain
View: Sun Microsystems, 1996), §1.2.3, “Architecture Neutral and Portable.”

410 JAVA RULES


byte, short, char, and boolean specific machine instructions. As stated
in the JVMS:
Given the Java virtual machine's one-byte opcode size, encoding types
into opcodes places pressure on the design of its instruction set. If
each typed instruction supported all of the Java virtual machine's runt-
ime data types, there would be more instructions than could be repre-
sented in a byte.12

Most programmers know about the computational types, but you may not know
how int bytecodes are made to work for byte, short, char, and
boolean values. Here is a very simple example:

class Test {
public static void main(String[] args) {
byte b = 99;
b++;
}
}

The decompiled bytecodes for the main method are as follows.

Method void main(java.lang.String[])


0 bipush 99
2 istore_1
3 iload_1
4 iconst_1
5 iadd
6 i2b
7 istore_1
8 return

Unlike boolean values discussed above, the value of a byte can be stored
directly in the bytecodes that implement a method. That is precisely the case
with the bipush machine instruction. The 99 you see in this decompiled code
is actually an operand that immediately follows the machine instruction. The bi
in bipush stands for byte-to-integer push. The value 99 is sign-extended and
pushed onto the operand stack as an integer. Thus bipush is how the byte

12. Gosling et al., §3.11.1, “Types and the Java Virtual Machine.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 411


data type is promoted. Note that all of the other instructions begin with the letter
i, which stands for int. In other words, they operate on int type values.
The next five instructions implement b++. The second element of the local
variable array is pushed onto the operand stack (iload_1). (The args param-
eter is stored in the first element of the local variable array.) Then the integer
constant 1 is pushed onto the operand stack (iconst_1). The iadd instruc-
tion is an integer add instruction that pops two integers off the stack and then
pushes back their sum. The next instruction is the one that interests us. The sum
is popped off the stack, truncated to a byte, and then pushed back on the
stack as an integer in the range of the byte data type ( i2b). The sum is then
popped off the stack and stored in the second element of the local variable array
(istore_1). Finally, the main method returns.
It is the addition of this i2b instruction that allows the JVM to use the int
instructions iload, iconst, and iadd to operate on a byte value. It would
be more efficient to have instructions designed specifically for the byte and
short data types. Then there would be no need to convert an int to a
byte in order to mimic overflow in the byte data type. For example, if b
is declared as an int, the same main method is compiled using only four
machine instructions, instead of the eight needed for a byte. This is a trade-off
in the design of the Java platform. It is both a reasonable and necessary trade-
off, however, because the byte and short data types are just not used that
much. For reasons that I do not want to take the time to explain, there is no such
performance penalty for the boolean and char data types.
The remainder of this section discusses unary and binary numeric promotion
in detail. The term unary numeric promotion refers to the operand of a unary
operator expression (for example, x++ or -1). Except for the erroneous
assumption that the operand of every unary operator expression is promoted,
unary numeric promotion is not very remarkable. Unary numeric promotion is
performed only for the unary plus and minus operators and the bitwise comple-
ment operator. This fact is largely hidden, however, by the implicit narrowing
conversions discussed in 5.7.1 Simple Assignment Conversion Context. For
example,

412 JAVA RULES


class Test {
public static void main(String[] args) {
byte b = +Byte.MIN_VALUE;
}
}

This compiles even though +Byte.MIN_VALUE is promoted to the int data


type because of implicit narrowing conversions. If, on the other hand, you
change the sign from + to -, the same example generates the following com-
piler error.

Test.java:3: possible loss of precision


found : int
required: byte
byte b = -Byte.MIN_VALUE;
^
1 error

This clearly shows that a byte value was promoted to an int. Unary numeric
promotion is not performed on the operand of an increment or decrement oper-
ator, as is suggested by the following quote.
For unary operators (such as ++ or --), the situation is very simple:
operands of the type byte or short are converted to int, and all
other types are left as is.13

This simply is not true. For increment or decrement operations, binary numeric
promotion is performed on the int value of +1 and the declared type of the
variable incremented or decremented. Furthermore, the type of an increment
or decrement operation is the declared type of the variable incremented
or decremented, not the promoted type as is suggested in the above quote.
If that type is a byte or short, then a narrowing primitive conversion is
required to store the result of the increment or decrement operation in the vari-
able denoted. Interestingly, we just decompiled such an example. The i2b
machine instruction discussed above is the narrowing primitive conversion.
If the operands of a binary operator are primitive numeric types, binary
numeric promotion is performed with one notable exception. In shift expres-

13. I lost this citation. Under the circumstances, I’m sure the author will not mind.

EXPRESSIONS, STATEMENTS, AND BLOCKS 413


sions, unary numeric promotion is performed on the left-hand operand (the value
shifted). The type of the operator expression is the promoted type of the value
shifted. The right-hand operand of a shift expression (the shift distance) is pro-
moted using unary numeric promotion, but this fact is of little or no significance
to a programmer because, as explained in 4.7.4 Unsigned Bytes, shift distances
are implicitly limited to 31 for an int and 63 for a long (the maximum number
of bits that can be shifted without zero filling their respective integral types).
Note that it may be useful to routinely assert that shift distances are less than
these implicit limits prior to using them in a shift expression.
Binary numeric promotion determines whether integer or floating-point arith-
metic is used to perform an operation, as well as the precision of the operation.
If either of the operand expressions evaluates to a floating-point type, then float-
ing-point arithmetic is used. Otherwise, integer arithmetic is used. Next, the pre-
cision of the operation is determined. The operand with the greatest precision
determines the precision of the operation, and consequently the type of the
operator expression. There are four possibilities that are evaluated in the follow-
ing order:
• If floating-point arithmetic is used, and either of the operand expressions
evaluates to the double type, then the operation is performed using IEEE
754 double precision, and the type of the operator expression is double.
Otherwise, the operation is performed using IEEE 754 single precision, and
the type of the operator expression is float.
• If integer arithmetic is used, and either of the operands is a long, then the
operation is performed using 64-bit integer precision, and the type of the
operator expression is long. Otherwise, the operation is performed using
32-bit integer precision regardless of the actual size of the integer (which
may be 64-bits in state-of-the-art systems), and the type of the operator
expression is int.
Finally, having determined whether integer or floating-point arithmetic is to be
performed and the precision of the operation, one or both of the operands may
have to be promoted. The only time both operands have to be promoted is when
both operands are either byte or short. In that case, both operands would
have to be promoted to an int. In type conversion terminology, the so-called

414 JAVA RULES


“promotion” of primitive numeric type operands is referred to as a widening
primitive conversion. Widening primitive conversions are safe, and never
result in a loss of data. See 5.6.2.1 Widening Primitive Conversions for a discus-
sion.
If you want to get a better feel for the practical significance of numeric pro-
motion, I suggest reading 4.7.4 Unsigned Bytes.

4.3.2 Operator Order of Precedence and Parenthesized Expressions


When operations involve more than one operator, the operator order of pre-
cedence in Table 4.3 is used to determine the order in which the operations are
performed. After evaluating primary expressions (which includes parenthesized
expressions),14 unary operations are always performed first. The last operations
performed are any conditional operations using the ternary ?: operator followed
by any assignment operations. Sandwiched in-between are the ten left-associa-
tive binary operations.
Mnemonics are sometimes used to remember the operator order of prece-
dence. The beginning and ending of Table 4.3 are easily remembered. The oper-
ator order of precedence for the ten left-associative binary operators can be
reduced to the following, which is just as easy to remember and more meaning-
ful than any nonsensical mnemonic.

MAth Shift <Numerical comparison> Equality & ^ | && ||

MAth because the Multiplicative operators have a higher order of precedence


than the Addition operators. You should not have any problem remembering that
the shift operators follow MAth operators because the shift operators are some-
times used for multiplication and division by powers of two. Failing any of this
you might try “Microsoft is nutty” (where the ‘y’ in nutty is a mnemonic for the
Equality operators). There is a pattern to the remaining operators; & comes
before | and && before ||.

14. I use parenthesized expression instead of parenthetical expression in deference to 15.8.5


Parenthesized Expressions in the JLS.

EXPRESSIONS, STATEMENTS, AND BLOCKS 415


Table 4.3 Operator Order of Precedence
Precedence Level Operator(s) Associativity

Unary operators a ++ -- + - ~ ! Right


(Type) expr

B Multiplicative operators * / % Left

C Additive operators + - Left

D Shift operators << >> >>> Left

E Numerical comparison operators < > <= >= Left

F Equality operators == != Left

G AND & Left

H exclusive OR ^ Left

I inclusive OR | Left

J Conditional-And && Left

1) Conditional-Or || Left

Conditional operator ?: Right

Assignment operators = Right


+= -= *= /=
&= |= ^= %=
<<= >>= >>>=
a. Although somewhat unusual, this book regards the cast operator as just another unary operator. There is
an explanation for this further down in this section.

There appears to be some confusion about the cast operator level of prece-
dence in relation to the unary operators. The JLS does not have an operator
order of precedence table and is silent on this matter. The cast operator is
clearly right-to-left associative as are the unary operators. For example,

int x = (int)(float) 2.5;

416 JAVA RULES


This compiles and the value of x is 2. I believe the confusion stems from the fol-
lowing example.

class Test {
public static void main(String[] args) {
int x = 0;
System.out.println(++(int)x); //COMPILER ERROR
System.out.println(--(int)x); //COMPILER ERROR
}
}

If the cast operator has the same level of precedence as the unary operators
and is also right-associative, then you might expect this program to compile. It
does not, but the reason is precisely because the cast operator does have the
same level of precedence as the other unary operators and is right-asso-
ciative. The order of evaluation is therefore ++((int)x) and --((int)x).
The problem is that ((int)x) evaluates to zero, which is not a variable. Here
are the compiler error messages:

Test.java:4: unexpected type


required: variable
found : value
System.out.println(++(int)x); //does not compile
^
Test.java:5: unexpected type
required: variable
found : value
System.out.println(--(int)x); //does not compile
^
2 errors

As discussed in the next section, nothing can come between a prefix increment
or decrement operator and the name of the variable being incremented or decre-
mented. With the exception of this prefix increment and decrement operator
anomaly, however, the cast operator behaves as would be expected when used
with other right associative operators that have the same level of precedence
(i.e., with other unary operators). For example,

class Test {
public static void main(String[] args) {

EXPRESSIONS, STATEMENTS, AND BLOCKS 417


System.out.println((int)~0);
System.out.println(~(int)0);
System.out.println((int)+0);
System.out.println(+(int)0);
System.out.println((int)-0);
System.out.println(-(int)0);
int x = 0;
System.out.println((int)++x);
System.out.println((int)x++);
System.out.println((int)--x);
System.out.println((int)x--);
System.out.println((boolean)!false);
System.out.println(!(boolean)false);
}
}

Executing this program prints

-1
-1
0
0
0
0
1
1
1
1
true
true

The larger lesson here is that the cast operator is just another unary opera-
tor. This means parentheses are not required when the expression following a
unary operator includes a cast operator. For example,

offset = (~(int)count & 3) << 3;

This line of code is from a class in the sun package. It also means that the
cast operator never has more than one operand. In the above line of code,
count is cast to an int, not count & 3. The “Code Conventions for the Java
Programming Language” document says:
Casts should be followed by a blank space. Examples:

418 JAVA RULES


myMethod((byte) aNum, (Object) x);
myMethod((int) (cp + 5), ((int) (i + 3)) + 1);

[end of quote]15

If the cast operator is a unary operator, why treat it differently from the other
cast operators? I would say just the opposite; never use a space after a cast
operator. Doing so is a constant reminder that the cast operator is a unary opera-
tor.
I have elected not to include the instanceof operator in this table,
though it certainly does have an order of precedence, as can be seen in the fol-
lowing program.

class Test {
public static void main(String[] args) {
boolean TRUE = true;
/*
* Lower than ! (and therefore all unary operators)
* and the cast operator because removing the
* parentheses generates compiler error.
*/
if (!("Hello World!" instanceof String))
System.out.println("passed test #1");
if ((boolean) ("Hello World!" instanceof String))
System.out.println("passed test #2");

//higher than == and therefore != (testing both operands)


if ("Hello World!" instanceof String == true)
System.out.println("passed test #3a");
if (true == "Hello World!" instanceof String)
System.out.println("passed test #3b");

//higher than && and therefore || (testing both operands)


if ("Hello World!" instanceof String && TRUE)
System.out.println("passed test #4a");
if (TRUE && "Hello World!" instanceof String)
System.out.println("passed test #4b");

15. Unascribed, “Code Conventions for the Java Programming Language” available online at
java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html, (Mountain View: Sun Micro-
systems, 1995-1999), §10.5.1, “Parentheses.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 419


}
}

Executing this program prints

passed test #2
passed test #3a
passed test #3b
passed test #4a
passed test #4b

The lesson here is first that parentheses are always required when negating the
value of an instanceof operator expression (or any operator expression for
that matter). It is doubtful that you would ever cast the value of such an expres-
sion (in what would necessary be a boolean identity conversion) and none of
the other operators above == and != in Table 4.3 Operator Order of Precedence
have boolean operands, so the other lesson is that parentheses are never
required around an instanceof operator expression except to negate it.
The remainder of this section discusses when parentheses are required. The
advice on this subject that can be found in Java books is wildly contradictory.
The reason for this is that some technical writers and software engineers regard
the use of parentheses as explicit precedence. As stated in the JLS:
Java programming language implementations must respect the order
of evaluation as indicated explicitly by parentheses and implicitly by
operator precedence.16

This is precisely why the definition of primary expression in the JLS includes
parenthesized expressions. Primary expressions (and therefore parenthesized
expressions) are always the first thing evaluated in any operator expression. The
more confident a programmer is in his or her knowledge of the normal order of
evaluation (as dictated by the operator order of precedence), however, the less
parentheses are required.
Most programmers know that the multiplicative operators have a higher
order of precedence then the additive operators. For example,

16. Gosling et al., §15.7.3, “Evaluation Respects Parentheses and Precedence.”

420 JAVA RULES


(a + b) * c

In this case, parentheses are required in order to override the normal order of
evaluation. The addition operator in the subexpression a + b has a lower order
of precedence than the multiplication operator. Unless parentheses are used,
the operator expression will be evaluated as follows.

a + (b * c)

If this is the proper order of evaluation, whether parentheses should be used is a


matter of style. They are definitely not required, but some programmers think
they make the expression more readable.
The problem is when parentheses are used to excess. The “Code Conven-
tions for the Java Programming Language” document is not very useful in this
regard. It suggests a liberal use of parentheses without any regard for operator
order of precedence:
It is generally a good idea to use parentheses liberally in expressions
involving mixed operators to avoid operator precedence problems.
Even if the operator precedence seems clear to you, it might not be to
others-you shouldn't assume that other programmers know prece-
dence as well as you do.

if (a == b && c == d) // AVOID!
if ((a == b) && (c == d)) // RIGHT

[end of quote]17

Bonkers! This advice must be balanced against the following quote from The
Java Programming Language, which is coauthored by no less than Dr. Gos-
ling.
Our use of parentheses is sparse—we use them only when code
seems otherwise unclear. Operator precedence is part of the language
and should be generally understood. Others inject parentheses liber-
ally. Try not to use parentheses everywhere—code becomes com-
pletely illegible…18

17. Unascribed, “Code Conventions for the Java Programming Language” available online at
java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html, (Mountain View: Sun Micro-
systems, 1995-1999), §10.5.1, “Parentheses.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 421


It often amazes me how contrary some of the advice is that is being shoveled at
Java programmers. This is one of those cases.
A better appreciation of the historical significance of the operator order of
precedence would go a long way in encouraging programmers to be more dis-
criminating in their use of parentheses. Not having studied advanced mathemat-
ics enough to fully understand the origin of the operator order of precedence, I
was fortunate enough to receive the following explanation in an email from Guy
Steele (one of the authors of the JLS).
Operator precedence in programming languages followed mathemati-
cal tradition, which evolved a few centuries ago so as to simplify the
notation of polynomials and relationships among polynomials. The rea-
son for the mathematical convention of giving multiplication
higher precedence than addition is to eliminate the need for
parentheses or other grouping marks when notating polynomi-
als. This explains why * is above + and + is above < and ==. The bool-
ean operators similarly place & above | and & [sic] because it simplifies
notation of boolean polynomials and disjunctive normal form. Because
the principal source of boolean values is the comparison operators, it
is convenient to rank comparisons above booleans. These observa-
tions dictate most of the precedence rankings in Java, C, PL/I, or For-
tran.19 [emphasis added]

I know how intimidating this must sound to some readers, but you do not have to
know what a polynomial is in order to understand from what Steele is saying that
the operator order of precedence evolved in mathematics precisely so that
“parentheses or other grouping marks” would not have to be used.
Although not directly related to the operator order of precedence, primary
expressions are always evaluated before the operator expressions in which they

18. Ken Arnold and James Gosling, The Java Programming Language, Third Edition (Reading:
Addison-Wesley, 1998), 178. Although I continually update references for new editions, this quote
has always been part of the book. For example, in the Second Edition it is on page 120. I suspect
very strongly that if it was not actually written by Dr. Gosling, that it was directly influenced by him.
19. I have had the good fortune of having had email exchanges with several of the authors of the
JLS and JVMS as well as other specifications (notably, John Rose, author of the Inner Classes
Specification ). I can assure you these are not only amazingly smart people, but I have found them
to be some of the nicest computer programmers I have ever met.

422 JAVA RULES


are used. It is therefore never necessary to enclose a primary expression in
parentheses. Yet all of the following examples of using parentheses around a
field access, array access, or method invocation expression are from the core
API.

flags &= ~(ImageObserver.ABORT);

dstMask[z] = ~(dstMax[z]);

return !(iterator().hasNext());

if (!(my.equalsIgnoreCase(his)))

if (!(mycomps.nextElement().equals(comps.nextElement())))

if (!(visBounds.equals(previousBounds)))

The use of parentheses to negate boolean methods is clearly a matter of style


(in large part because they are required when negating an operator expression),
but the first two examples seem peculiar.
There is one very special case in which parentheses are used around a pri-
mary expression. Actually, they are used to “break apart” a primary expression.
Anytime the primary expression in the primaryExpression.fieldName
or primaryExpression.methodName general form is cast, parentheses
are required around the primary expression. See 1.10.3 Casting a Target Refer-
ence for a discussion.
The single most common misuse of parentheses that I have identified is
around the operands of the && and || operators (as in the example marked
“RIGHT” from the “Code Conventions for the Java Programming Language” docu-
ment above). These operators have a very low level of precedence precisely so
that their operands will not require parentheses. Unless those operands include
the ?: conditional operator (very unlikely) or an assignment operator, paren-
theses are never required. Here are some examples from a deprecated
method in the core API that vacillates between using and not using parentheses
under these circumstances:

EXPRESSIONS, STATEMENTS, AND BLOCKS 423


if (box != null && box.group != this)

if ((oldChoice != null) && (oldChoice != box))

if (box != null && oldChoice != box && !box.getState())

I understand this is a matter of style, but I also think if more programmers under-
stood that parentheses are almost never required around the operands of the
&& and || operators, there would be less of them. Here is another example
from no less than the Boolean class:

if ((obj != null) && (obj instanceof Boolean))

I would not have used parentheses around the operands of the && operator in
either of these examples. Nor do I use them around the operands of the ||
operators.
One of the reasons why I am focusing on these two operators is to empha-
size that there is a considerable difference between using them separately and
together. When used separately, the meaning of && is always “if all of these
operands evaluates to true,” and the meaning of || is “if any one of these
operands evaluates to true. It does not matter how many times the operator is
used, the meaning is always the same. That simplicity is lost, however, if the &&
and || operators are used together in the same expression. This is one case in
which I think the most ardent advocate of memorizing the operator order of pre-
cedence would not only tolerate unnecessary parentheses, but might even use
them as a matter of style. For example,

class Test {
public static void main(String[] args) {
boolean a = true, b = false, c = true;
if (a && b || c)
System.out.print("Can you tell if this will print?");
}
}

Only one of the operands of an || operator has to be true in order for the
expression to evaluate to true, so the answer is somewhat obvious. The ques-
tion is: What is the left-hand operand of the || operator? Is it b or a && b. The

424 JAVA RULES


answer is the latter because the && operator has a higher level of precedence
than ||. The expression therefore evaluates as follows.

(a && b) || c

In this case, the use of parentheses makes it much easier to read the expres-
sion.
You should always remember that the unary operators (which include the
cast operator), the ?: conditional operator, and the assignment operators are
at the extremes of the operator order of precedence. There are a number of
simple rules that grow out of this.

If the operand of a unary operator is an operator expression (as


opposed to a primary expression), parentheses are always required.

Here is an example from a private method in the core API (method and vari-
able names have been changed):

private void setBit(int mask, boolean set) {


if (set) {
bitset |= (1 << mask);
} else {
bitset &= ~(1 << mask);
}
}

The parentheses in the first (1 << mask) are definitely not required because
assignment operators have the lowest level of precedence. They have a certain
aesthetic value, however, because the same parentheses are required in the
second assignment operation. Here is yet another example from the core API in
which a condition expression is negated:

for (int i=0; i<length; i++) {


Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}

EXPRESSIONS, STATEMENTS, AND BLOCKS 425


In both cases, an operator expression is being negated. This rule also covers
casting a binary operator expression. Without parentheses around the entire
cast expression, the cast operator is applied to the closet subexpression. For
example:

int i = (int) x * 1.5 //COMPILER ERROR

This is a very common programming error. It does not compile because the
entire binary operator expression must be cast. The correct way to write such
an expression is

int i = (int) (x * 1.5)

The other rule for operators at the extreme ends of the operator order of prece-
dence is stated as follows.

Any subexpression that uses either the conditional operator or an


assignment operator must be parenthesized because these opera-
tors have the lowest order of precedence.

Here is an example using an assignment operator:

while ((c = in.read()) != -1)


System.out.print((char)c);

This is a common example that writes the contents of a file one character at a
time to standard output.
The relationship between the conditional operator and the assignment opera-
tors is very special. The conditional operator has a higher level of precedence.
Normally that would mean that any operand that uses an assignment operator
would require parentheses, but only the third operand (to the right of the colon)
actually requires parentheses if an assignment operator is used. For example,

class Test {
static int x;
public static void main(String[] args) {
System.out.println(test(true));
System.out.println(test(false));
}

426 JAVA RULES


static int test(boolean b) {
return b ? x=0 : x=1;
}
}

Attempting to compile this program generates the following compiler error:

Test.java:8: unexpected type


required: variable
found : value
return b ? x=0 : x=1;
^
1 error

This is one of those compiler messages that does more harm than good. The
compiler is reading the return statement as follows.

return (b ? x=0 : x)=1;

If parentheses are placed around x=1 this example compiles. What makes the
relationship between these two levels of precedence special is that the compiler
uses ? and : to delimit the first and second operands. Parentheses are therefore
not required even if they are assignment expressions (such as x=0 in this exam-
ple). The JLS really should document this behavior.
The only other use of parentheses that I feel deserves special mention is
bits. Querying a bit requires the use of a bitwise AND or inclusive OR operators
as well as one of the equality operators. Here is a typical example from the core
API:

if ((eventMask & AWTEvent.ACTION_EVENT_MASK) != 0 ||


actionListener != null)

The bitwise operators have a lower order of precedence than the equality opera-
tors and therefore always require parentheses in this context. There are numer-
ous other examples of this in 4.7.1 Bits.
Parentheses are never required around the expression in a return
statement. They are sometimes used as a matter of style, but this should be
limited to conditional expressions using the ?: operator. Using parentheses

EXPRESSIONS, STATEMENTS, AND BLOCKS 427


around the expression in a return statement is also discouraged in the “Code
Conventions for the Java Programming Language” document.20

4.3.3 The Associative Property of Operators


All of the operators at a given level of precedence in Table 4.3 have the same
associative property. They are either left- or right-associative. All of the opera-
tors at the highest and lowest levels of precedence are right associative. Those
are all of the unary operators, the conditional operator, and all of the assignment
operators. The ten binary operators in between are all left-associative, which is
very convenient because otherwise you would have to remember their order of
precedence and associative property.
The associative property of an operator determines the order in which the
operations are performed in complex operator expressions when there is two
or more operators that have the same level of precedence. For example:
a + b - c

The plus and minus operators have the same level of precedence and are left
associative (as are all of the binary operators). Therefore, the computer evalu-
ates this expression as if it were written (a + b) - c.
Any combination of unary operators is valid except the following.
• The operand of a increment or decrement operator must denote a variable.
Hence, none of the other unary operators can come between a prefix incre-
ment or decrement operator and the variable name. The problem is that
these operators are right-associative, and the result of the first unary opera-
tor expression is a value, not a variable. A special case of this rule is the
combination +++ and ---. This is not allowed because the parser inter-
prets them as a prefix increment operator followed by unary plus operator
and as a prefix decrement operator followed by a unary minus operator,
respectively.
• For any given unary expression, only one prefix increment operator, prefix
decrement operator, postfix increment operator, or postfix decrement oper-
ator can be used. In other words, these unary operators are mutually exclu-

20. Unascribed, §7.3, “return Statements.”

428 JAVA RULES


sive. The reason for this is simple. Because unary operators are right
associative, postfix operators are executed before prefix operators. Hence,
expressions such as –-x++ would be evaluated as –-(x++). The prob-
lem again is that the result of the expression x++ is a value, not a variable.
The following are examples of expressions with more than one unary operator.

x = ~a--;
x = -++a;
x = -+~a++;

All of these examples compile. Note that such compounded unary expressions
cannot be used in expression statements:

~a--; //COMPILER ERROR


-++a; //COMPILER ERROR
+-~a++; //COMPILER ERROR

Because unary operators are right-associative, these examples are a bitwise com-
plement expression, unary minus expression, and unary plus expression, respec-
tively. As such, they cannot be used in expression statements. Attempting to
compile them generates not a statement error messages. This is significant
because increment and decrement operators can be used in expression state-
ments (because they have side effects). It is interesting to note that prior to the
1.3 release, these same lines of code would generate invalid expression
statement compiler error messages.
The numerical comparison and equality operators are sometimes described
as non-associative. Technically speaking, they are left-associative, but some
explanation is required. As stated in the JLS:
The relational operators are syntactically left-associative (they group
left-to-right), but this fact is not useful.21

The equality operators are syntactically left-associative (they group left-


to-right), but this fact is essentially never useful.22

21. Gosling et al., §15.20, “Relational Operators.”


22. Gosling et al., §15.21, “Equality Operators.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 429


Relational expressions such as a < b > c do not compile because the operands
of the numerical comparison operators must be primitive numeric types. Assum-
ing the subexpressions are true, (a < b) > c would evaluate to true > c,
which does not compile because (as already stated) the operands of a numerical
comparison operator must be primitive numeric types (not boolean).
Equality expressions share a similar problem which makes them inherently
meaningless. For example,

if (a == b == c)
then statement;

This expression is evaluated as (a == b) == c, because of the fact that equal-


ity operators are technically left-associative. If the value of (a == b) is true,
the resulting subexpression true == c does not even compile unless c is a
boolean type variable. Now consider that all three operands are boolean
type variables that evaluate to false. One would naturally expect the expres-
sion a == b == c to evaluate to true, but it does not. The subexpression (a
== b) is true because both a and b are false, and the resulting subexpres-
sion true == b is false. Obviously, equality expressions such as these are
“essentially never useful” (as stated in the JLS) even if they do compile.

4.3.4 Nondestructive Operators


Shift operations are said to be nondestructive, meaning that the value of the
left-hand operand is unchanged as the result of evaluating a shift expression. For
example,

int a = 1;
int b = a << 1;
int c = b << 1;
System.out.println(a);
System.out.println(b);
System.out.println(c);

These diagnostic messages print

1
2
4

430 JAVA RULES


To both shift and store the value of a variable, use one of the three compound
assignment operators <<=, >>=, or >>>=.
The shift operators, however, are not the only “nondestructive” operators.
With the obvious exception of increment and decrement operators, the value of a
variable is the same after as before evaluating a unary expression. For example,

int a = 25;
int b = 25;
int c = 25;
boolean d = true;

int x;
boolean y;

x = +a;
x = -b;
x = ~c;
y = !d;

if (a == 25 && b == 25 && c == 25 && d == true)


System.out.println("this message does print");

The term nondestructive could be used to describe these unary operators also.
This point can be taken even further by adding the final modifier to the decla-
ration of a, b, c, and d.

4.4 Exceptions are Precise


This section relates directly to the order of evaluation in both primary and opera-
tor exceptions. Exceptions are thrown from within the body of some method or
constructor. In the case of variable initializers, initialization blocks, and construc-
tors, exceptions are thrown from within the body of one of the special initializa-
tion methods. Methods complete abruptly at the precise point (the point-of-
origin) at which the exception is thrown. As stated in the JLS:
Exceptions are precise: when the transfer of control takes place, all
effects of the statements executed and expressions evaluated before
the point from which the exception is thrown must appear to have taken
place. No expressions, statements, or parts thereof that occur after

EXPRESSIONS, STATEMENTS, AND BLOCKS 431


the point from which the exception is thrown may appear to have been
evaluated. 23

If the exception is thrown in a subexpression, the expression of which it is a part


completes abruptly for the same reason. The same is true of any statements or
blocks of code (including notably try blocks) that are executing at the time. In
short, the transfer of control is immediate. For example,

class Test {
static {System.out.println("loading program");} // <clinit>
public static void main(String[] args) {
System.out.println("main");
System.out.println("invoking a()");
a();
System.out.println( "end of program");
}
static void a() {
System.out.println("invoking b() in a try-finally");
try {
b();
}
finally {
System.out.println("finally blocks are always executed");
System.out.println("STACK TRACE TO FOLLOW…");
}
}
static void b() {
System.out.println("invoking c()");
c();
}
static void c() {
int i = 0;
System.out.println("ABOUT TO DIVIDE AN INTEGER BY ZERO");
i /= 0;
System.out.println("exceptions are precise");
}
}

Executing this program prints:

23. Ibid., §11.3.1, “Exceptions are Precise.”

432 JAVA RULES


loading program
main
invoking a()
invoking b() in a try-finally
invoking c()
ABOUT TO DIVIDE AN INTEGER BY ZERO
finally blocks are always executed
STACK TRACE TO FOLLOW…
Exception in thread "main" java.lang.ArithmeticException: / by
zero
at Test.c(Test.java:26)
at Test.b(Test.java:21)
at Test.a(Test.java:12)
at Test.main(Test.java:6)

Stack traces are discussed in 6.10.2 A Stack Trace Primer. The point to under-
stand now that “abrupt” completion is just that. Control is immediately trans-
ferred to the first statement in the catch block, which explains why neither
“exceptions are precise” nor “end of program” print.
There are two exceptions to the rule that exceptions are precise. Expres-
sions that can throw a NullPointerException do so only after all of the
subexpressions are fully evaluated. This includes method invocation expressions
and array access expressions. The following is an example of throwing a
NullPointerException late:

class Test {
static String s;
public static void main(String[] args) {
try {
s.equals(s = "abc");
}
catch(Exception e) {
System.out.println(e);
System.out.print("s = " + s);
}
}
}

Executing this program prints

java.lang.NullPointerException
s = abc

EXPRESSIONS, STATEMENTS, AND BLOCKS 433


The target reference s is evaluated first. However, before throwing a Null-
PointerException (because s is null), the argument expression s =
"abc" is evaluated.
The other exception to the rule that exceptions are precise is an Array-
IndexOutOfBoundsException when thrown from the left-hand operand of
a simple assignment statement (emphasis on simple). For example,

class Test {
public static void main(String[] args) {
String[] strings = new String[10];
String s = null;
strings[99] = printSomething();
}
static String printSomething() {
System.out.println("ArrayIndexOutOfBounds is thrown late");
return "";
}
}

Executing this program prints

ArrayIndexOutOfBoundsException is thrown late


Exception in thread "main"
java.lang.ArrayIndexOutOfBoundsException
at Test.main(Test.java:5)

Although the left-hand operand is evaluated first, the rather obvious Array-
IndexOutOfBoundsExpression is not thrown until after the right-hand
operand has been evaluated.
There is an exception to both of these exceptions to the rule that exceptions
are thrown late. If that sentence is not enough to confuse you, the exception to
the exceptions involve the left-hand operand of a compound assignment opera-
tor, in which case both NullPointerException and ArrayIndexOut-
OfBoundsException are precise. If the left-hand operand of a compound
assignment expression throws either a NullPointerException or an
ArrayIndexOutOfBoundsException, the exception is in fact thrown
before any part of the right-hand operand is evaluated. The necessity for this
exception to the exception is the “fetch and save” behavior of compound assign-

434 JAVA RULES


ment operators. The JVM fetches and saves the value of the left-hand operand
in a compound assignment operation before evaluating the right-hand operand.
For example,

int i = 12;
i = i + (i = 3);

Because of the left-to-right evaluation of subexpressions, i is equal to 15. The


second assignment expression can be written as follows.

int i = 12;
i += (i = 3);

Because of the “fetch and save” behavior of compound assignment operators, i


is also equal to 15 in this example. The reason why NullPointer-
Exception and ArrayIndexOutOfBoundsException are precise in a
compound assignment operation is because they make it impossible to “fetch
and save” the value of the left-hand operand.

4.5 The 38 Unary, Binary, and Ternary Operators


The number of operands in an operation defines the operator as unary, binary,
or ternary: unary operators have one operand; binary operators have two
operands; ternary operators have three operands. The 38 unary, binary, and
ternary operators discussed in this chapter breakdown as follows:
6. Unary operators
31. Binary operators
1. Ternary operator
38. Total
Unary operators have a higher level of precedence than binary and ternary oper-
ators. Twelve of the 31 binary operators are the simple and compound assign-
ment operators, which have a lower order of precedence than the ternary ?:
operator; otherwise, this list would correspond to the operator order of prece-
dence in Table 4.3 on page 416. The unary operators are either prefix opera-
tors or postfix operators. All of the binary operators are infix binary
operators, which simply means that, like the operator expression 2 + 2, they
have a left-hand operand and a right-hand operand.

EXPRESSIONS, STATEMENTS, AND BLOCKS 435


The JLS has very specific names for all of the operators. For example, the
== and != operators are referred to as either Boolean equality operators or
reference equality operators, depending on the operand types. The operator
tables in this chapter such as Table 4.5 Negation Operators on page 439
include the operator names exactly as they appear in the JLS. The reader should
be aware, however, that general usage is very lax about the names of operators
and operator expressions. For example, the “conditional operator” (which is the
proper name for the ?: operator as used in the JLS) is also known as:
• If-else operator
• Question/colon operator
• Ternary operator
• Ternary if-else operator
• The triadic operator
Why are there so many names for the same operator? Perhaps because the ?:
operator is no more or less conditional than the && or || operators. The point is
that you should become accustomed to alternative names for operators.
Operator expressions (as opposed to the operator in an operator expres-
sion) do not have clearly defined names in the JLS. Even when they do, the
names are not always intuitive. For example, a diligent search of the JLS reveals
that the numerical comparison operators (>, <, >=, <=) are used in rela-
tional expressions, as is any expression that uses the instanceof type com-
parison operator. I find it easier to refer to all expressions that are not primary
as operator expressions rather than using different names based on the opera-
tor. If you would rather have more specific names, just use the operator name
followed by “expression.” Thus the addition operator is used in addition
expressions, and shift operators are used in shift expressions.

4.5.1 Increment and Decrement Operators -- and ++


The operand type can be any primitive numeric type, including notably the float-
ing-point types. The type of the operator expression is the declared type of the
variable incremented or decremented. As noted in Table 4.4, “postfix” means

436 JAVA RULES


Table 4.4 Increment and Decrement Operators
Operator Description

++ Increment Evaluates to the value of the variable before or after (prefix and postfix,
operator respectively) the result of the increment operation is stored in the
variable. The increment operation adds 1 to the value of the variable
(x++ is the same as x = x + 1).

-- Decrement Evaluates to the value of the variable before or after (prefix and postfix,
operator respectively) the result of the decrement operation is stored in the
variable. The decrement operation subtracts 1 to the value of the
variable (x-- is the same as x = x - 1 ).

the increment or decrement operation is performed after the value of a variable


is used in an expression. For example,

class Test {
public static void main(String[] args) {
int x = 1;
System.out.println(x++);
System.out.println(x);
}
}

Executing this program prints:

1
2

Here is an example of a “prefix” increment operator:

class Test {
public static void main(String[] args) {
int a = 2;
int b = 1;
System.out.println(a * ++b);
}
}

Executing this program prints 4. The value of a * ++b is 4 because b is incre-


mented before being used in the multiplication operation.

EXPRESSIONS, STATEMENTS, AND BLOCKS 437


When used in a top-level expression, the prefix and postfix operators are
equivalent. As a matter of style, however, the postfix operators tend to be used
in the ForUpdate part of a for statement header, while the prefix operators
tend to be used in expression statements. Some programmers think the incre-
ment and decrement operators should only be used as top-level expressions
because when embedded in larger expressions they are easily overlooked. None
of these are hard and fast rules, however. For example, increment operators are
often used in a loop to increment the value of an argument in a method invoca-
tion expression.

NOTE 4.1
In choosing to organize the unary operators as follows, I deliberately ig-
nore the unary plus operator (which does nothing). An unsigned nu-
meric literal is always positive, and never requires the + operator to
make it so. The explanation for including it in the language is typically
one of “symmetry” with the unary minus operator.

4.5.2 Negation Operators -, ~, and !


The section discusses the unary minus (-), bitwise complement (~), and logical
complement (!) operators. Why are these unary operators grouped together?
The bitwise and logical complement operators invert the value of their operands,
much like the unary minus operator inverts the sign bit of a number. For example
!true is equal to false. For that reason, the use of these operators is some-
times referred to as bitwise negation and logical negation (versus arith-
metic negation).
The type of the operands and operator expressions are as follows.
• The operand type of the unary minus operator must be a primitive numeric
type. The type of the operator expression is the promoted type of the oper-
and using unary numeric promotion.
• The operand type of the ~ bitwise complement operator must be a primi-
tive integral type (byte, short, char, int, or long). The type of the

438 JAVA RULES


operand expression is the promoted type of the operand using unary
numeric promotion. That is to say, the type of the operand expression is
either int or long.
• The operand type of the ! logical complement operator must be
boolean. The type of the operator expression is also boolean.
Table 4.5 also includes the operand types for your convenience.

Table 4.5 Negation Operators


Operator Operand Type Description

- Unary minus Any primitive Evaluates to the arithmetic negation of the


operator numeric type value of the operand expression.

~ Bitwise Primitive integral Evaluates to the value of the operand


complement types expression with each 0 bit set to 1, and each 1
operatora bit set to 0. Because of the two’s complement
format, ~x always equals negative x minus one
or (-x)-1 . For example, the bitwise
complement of +10 is -11, and the bitwise
complement of -100 is +99 . Shown in
binary, the latter would be:

~ 10011100 (-100) equals


01100011 (+99)

! Logical boolean Evaluates to false if the value of the


complement operand expression is true, and vice versa.
operator
a. One non-standard name for this operator that I particularly like is bit flipper. Complement and compliment
are pronounced exactly the same, so be careful to spell the names of these operators with an ‘e’ in the middle.
This is also true of the two’s complement format of the integral types.

Arithmetic negation merely changes the sign of a number from positive to


negative, or vice versa. For example,

System.out.println(– (+1));
System.out.println(– (–1));
System.out.println(– (+0));
System.out.println(– (–0));

EXPRESSIONS, STATEMENTS, AND BLOCKS 439


These diagnostic messages print

-1
1
0
0

Note that zero is not signed in the primitive integral types. They are, however, in
the floating-point types. For example,

System.out.println(-(-0.0));
System.out.println(-(+0.0));
System.out.println(– (Double.POSITIVE_INFINITY));
System.out.println(– (Double.NEGATIVE_INFINITY));

These diagnostic messages print

0.0
-0.0
–Infinity
Infinity

While not uncommon, the bitwise complement operator is certainly one of


the least used of all the operators, second perhaps to only the exclusive OR
operator (^). It is almost always used in one of the following two contexts.
• It is sometimes used in the simple hash algorithms implemented by the
hashCode() method.
• By far the most common use of this operator is to unconditionally set a bit
(sometimes referred to as a flag) to zero. The following is a typical example
of this programming technique.

flag &= ~BIT_MASK; //sets MASKed bit in flag to 1

This is why ~ is best grouped with the other “bitwise” operators &, ^, and |
rather than thinking of it as a unary operator. All four of these bitwise opera-
tors are discussed in 4.7 A Bitwise Primer along with the shift operators.
The logical complement operator is almost always used in Boolean control
flow expressions to negate either a boolean type variable or a method that
returns a boolean value. Interestingly enough, this leads to a very subtle nam-

440 JAVA RULES


ing convention for such entities. A boolean type variable or method usually
has a name that suggests the value is true. For example,

if (!serialized) {

if ((next != null) && !next.visible){

if (!hashCodeCached) {
hashCode = hashCode();
hashCodeCached = true;
}

Do you see how naturally these read because the variable names suggest the
value is true? Here is another example involving a method that returns a
boolean value:

Iterator iterator = c.iterator();


while (iterator.hasNext()) {
if(!contains(iterator.next()))
return false;
}
return true;

This is one possible implementation of the containsAll(Collection c)


bulk method in the Collection Framework. Note that the ! operator is on the
inside of the parentheses. Nothing ever goes outside the parentheses around a
Boolean control flow expression in an if, while, or do statement, including
the logical complement operator. This means a second set of parentheses are
required whenever the expression is an operator expression as opposed to a pri-
mary expression. For example,

if (!(o1==null ? o2==null : o1.equals(o2)))


return false;

Here a second set of parentheses is required in order to negate the entire condi-
tional expression. If both of the operands are boolean, however, a != b is
the same as !(a == b) and is an easier read.
After boolean type variables and methods, one of the most common uses of
the ! operator in Boolean control flow expressions is in front of a parenthesized
expression using the instanceof type comparison operator. The following

EXPRESSIONS, STATEMENTS, AND BLOCKS 441


example from the ThreadGroup class is commonly referred to as the default
exception handler.
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
e.printStackTrace(System.err);
}
}

The instanceof operator is in fact an operator and therefore must be paren-


thesized if negated.
The logical complement operator can also be used in front of parenthesized
expressions using the && and || operators, both of which evaluate to a
boolean type. Here is an example from the BigInteger class:

while (!((j==0 && z.equals(ONE)) || z.equals(w.subtract(ONE)))) {


if (j>0 && z.equals(ONE) || ++j==a)
return false;
z = z.modPow(TWO, w);
}

As noted in 4.3.2 Operator Order of Precedence and Parenthesized Expres-


sions, the parentheses around (j==0 && z.equals(ONE)) are not really
necessary because && has a higher level of precedence than ||, but expres-
sions using both of these operators should always use parentheses to clarify the
meaning. As shown in Table 4.6, this is a very special use of the logical comple-
ment operator.

Table 4.6 The Complete Set of Logical Expressions


Operators Logical Meaning

if (a && b && c…) If all subexpressions evaluate to true

if (a || b || c…) If any subexpression evaluates to true

if (!(a && b && c…)) If any subexpression evaluates to false

if (!(a || b || c…)) If all subexpressions evaluate to false

442 JAVA RULES


Note that this table assumes boolean type operands. There is no way of say-
ing if (x = a || b || c). Instead you have to repeat the x. For example,
if (x == a || x == b || x == c). The same is true when using &&. If you
can remember this table negating logical expressions will require a lot less
thought. Here is an example from an equals(Object obj) method:

if (!(this.type.equals(that.type) &&
this.name.equals(that.name) &&
this.actions.equals(that.actions)))
return false;

This if statement returns false if “any” of these instance variables are not
equal.
There is just one other use of the logical complement operator that deserves
special mention. The type of a numerical comparison operation is boolean,
which means they can be negated using the logical complement operator. Here
are a couple examples from the core API:

if (!(size > 0)) {


throw new IllegalArgumentException("negative send size");
}

if (!('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'))
break;

I find such expressions hard to read and avoid them as a matter of style. Here
are the equivalent numerical comparison operations stated positively:

if (size <= 0) {
throw new IllegalArgumentException("negative send size");
}

if (c < 'A' || (c > 'Z' && c < 'a') || c > 'z')


break;

The first change helps to point out a problem with the detail message. I know it
is controversial to say so, but there is always a positive alternative to negating
the result of a numerical comparison operation.

EXPRESSIONS, STATEMENTS, AND BLOCKS 443


4.5.3 The Elementary School Operators
This section discusses the arithmetic operators ( *, /, +, -), numerical compari-
son operators (>, <, >= , <=), and equality operators (==, != ). The section is so-
named to emphasize that these operators are used no differently in a computer
programming language than they were when you were first introduced to them in
elementary school. Consequently, you should be able to read through this sec-
tion in about five minutes.

Table 4.7 The Elementary School Operators


Operator Description

* multiplication Evaluates to the product of the two operands. The left-hand operand
operator is the multiplicand. The right-hand operand is the multiplier.

/ division Evaluates to the quotient of the two operands. The left-hand


operator operand is the dividend. The right-hand operand is the divisor. For
integer arithmetic, the remainder, if any, is simply truncated. The JLS
calls this “rounding to zero.”

+ addition Evaluates to the sum of the two operands. Both operands are
operator addends.

- subtraction Evaluates to the difference of the two operands. The left-hand


operator operand is the minuend. The right-hand operand is the subtrahend.

< numerical Evaluates to true if the value of the left-hand operand is less than
comparison the value of the right-hand operand. Otherwise, the expression
operator evaluates to false.

> numerical Evaluates to true if the value of the left-hand operand is greater
comparison than the value of the right-hand operand. Otherwise, the expression
operator evaluates to false.

<= numerical Evaluates to true if the value of the left-hand operand is less than
comparison or equal to the value of the right-hand operand. Otherwise, the
operator expression evaluates to false.

>= numerical Evaluates to true if the value of the left-hand operand is greater
comparison than or equal to the value of the right-hand operand. Otherwise, the
operator expression evaluates to false.

444 JAVA RULES


Table 4.7 The Elementary School Operators
Operator Description

== equality Evaluates to true if the value of the left-hand operand is equal to


operator the value of the right-hand operand. Otherwise, the expression
evaluates to false.

!= equality Evaluates to true if the value of the left-hand operand is not equal
operator to the value of the right-hand operand. Otherwise, the expression
evaluates to false.

This group of operators include the only two operators in the Java program-
ming language that throw an exception. Those are the / and % operators, which
throw ArithmeticException if the divisor (the right-hand operand) is
zero.24
The operands of the arithmetic and numerical comparison operators must
be primitive numeric types. The type of the operator expression is the promoted
type of the operands using binary numeric promotion.
The equality operators work with all data types, but the operands must have
matching types. That is to say, both operands must be primitive numeric
types, the boolean type, or reference types. The concept of matching types
largely corresponds to the type conversion boundaries in Figure 5.4, “Type Con-
version Boundaries” on page 595. For example, if the type of the left-hand oper-
and is a reference type, the type of the right-hand operand must be either
another reference type or the special null type, else the equality expression will
not compile. If the operand types are primitive numeric types, they are promoted
using binary numeric promotion. Depending on the operand types, equality oper-
ators are referred to as numerical equality operators, Boolean equality
operators, or reference equality operators. Equality expressions always
evaluate to the boolean type.

24. ArithmeticException is discussed in a section that was moved to Volume 1 (along with
the sections on floating-point arithmetic and rounding modes) after the first edition of that book was
published. A cross-reference to the correct section will be added to the Second Edition of this vol-
ume if and when the Second Edition of Volume 1 is published. I eagerly await that day.

EXPRESSIONS, STATEMENTS, AND BLOCKS 445


One of the peculiar things about the equality operators is that you have to
repeat the left-hand operand over and over again in order to determine if it is
equal (or not equal) to more than one value. Here is an example from the core
API:

if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE)

There is no way around this in the Java programming language. See also Table
4.6 The Complete Set of Logical Expressions on page 442.
Finally, there are a few miscellaneous notes. The expression a != b is the
same as !(a == b), regardless of the operand type. In Boolean equality, the
expression false == false is true. The expression a != b is also the
same as a ^ b, if the operands are boolean.

4.5.4 Remainder Operator %


The remainder operator (%) is right up there with the exclusive OR operator (^)
and the bitwise negation operator ( ~) in terms of being one of the least used of
all operators. For example, the most recent Window’s implementation of the
J2SE has in excess of 3,500 classes (a number which includes the core API). Of
these, only about 70 or 2% use the remainder operator at all.
Unlike the C and C++ programming language, the operands of a remainder
operation can be one of the floating-point types. If the operands of a division
operation are floating-point types, the value of the division operation includes
what most of us think of as the remainder. For example, using a calculator 5.5
divided by two is equal to 2.75. The .75 is what we normally think of as the
remainder. It is expressed as a percentage of the divisor ( 2 x .75 = 1.5). The
1.5 is what you get using the remainder operator. For example,

class Test {
public static void main(String[] args) {
double dividend = 5.5, divisor = 2, quotient, remainder;
quotient = dividend/divisor;
remainder = dividend%divisor;
System.out.println("quotient = " + quotient);
System.out.println("remainder = " + remainder);
}
}

446 JAVA RULES


Executing this program prints

quotient = 2.75
remainder = 1.5

The fact is that the remainder operator is seldom used to obtain the remainder
from the implied division operation. How then is it used?
A comparison can be made to the shift operators, which always multiply or
divide the left hand operand by a power of two, but are seldom used with this is
mind. Likewise, the value of a remainder operation is always the remainder of an
implied division of the left-hand operand by the right-hand operand. Neverthe-
less, that is not how the operator is used. Most of the time the % operator is
used as a modulo operator to reduce a number (the left-hand operand or
dividend) modulo the divisor, the result of which is a number in a known
range of values. Sound funny? Welcome to modular arithmetic, which is
defined as follows in Webster’s online dictionary.
arithmetic that deals with whole numbers where the numbers are
replaced by their remainders after division by a fixed number <in a
modular arithmetic with modulus 5, 3 multiplied by 4 is 2>25
In other words 12%5 equals 2. In modular arithmetic, 12 modulo 5 is read
“repeatedly subtract 5 from 12 until the difference is less than 5.” It is a mathe-

Though a commonly used programming technique prior to the intro-


duction of the nextInt(int n) method in the 1.2 release, using
random.nextInt() % 100 (where random is an instance of
the java.util.Random class) to generate random numbers
between zero and 99 is seriously flawed. See the API docs for the
nextInt(int n) method in the Random class.

matical certainty that the value of a remainder operation will be less than the divi-
sor (the right-hand operand) and have the same sign as the dividend (the left-
hand operand). If the remainder operation uses integer math, for example, the

25. Merriam-Webster's Collegiate Dictionary online (www.m-w.com).

EXPRESSIONS, STATEMENTS, AND BLOCKS 447


result will be an integer in the range of zero to the value of the divisor minus one.
In other words, this is a right-open interval in which the divisor is comparable to
the length field of an array. For example,

hashCode = key.hashCode();
index = (hashCode & 0x7FFFFFFF) % hashTable.length;

This example of computing an index for a hash table is from 4.11.2 Understand-
ing Hash Tables in Volume 1. The only reason HashTable and other hash-
based collections do not inexplicably throw an ArrayIndexOutOfBounds-
Exception when accessed is because of % hashTable.length (reducing
the index modulo the hash table size) in this algorithm.
The other common use of the remainder operator is in an equality expres-
sion (n % x == 0 or n % x != 0) which is read “n is (or is not) a multiple of the
x.” For example,

public void initialize(int strength, SecureRandom random) {


if ((strength < 512)||(strength > 1024)||(strength % 64 != 0)){
throw new InvalidParameterException
("Prime size must range from 512 to 1024 "
+ "and be a multiple of 64");
}

This argument check is from the DSAKeyPairGenerator class in the


sun.security.provider package. Here is another example from the
DecimalFormat class in the java.text package:

// Output grouping separator if necessary. Don't output a


// grouping separator if i==0 though; that's at the end of
// the integer part.
if (isGroupingUsed() &&
i>0 &&
(groupingSize != 0) &&
(i % groupingSize == 0))
{
result.append(grouping);
}

448 JAVA RULES


Grouping separators and the DecimalFormat class are discussed in Chapter
4, “Strings and Other Common Data Types” of Volume 1. In many countries
including the United States, the grouping separator for a decimal number is a
comma, in which case this code would be inserting a comma in between hun-
dreds, thousands, millions, etc. Here is one more (slightly reformatted) example
from the GregorianCalendar class in the java.util package:

return year >= gregorianCutoverYear ? ((year % 4 == 0) &&


((year % 100 != 0) || (year % 400 == 0))) : // Gregorian
(year % 4 == 0); // Julian

I bet you can you tell what this code is doing. It is the return statement (which
in this case is the entire method implementation) from the isLeapYear(int
year) method.
One very common trick of the trade is to use the remainder operator to exe-
cute some code “every nth iteration” of a loop, where n is the divisor in a remain-
der operation. For example,

public static String toBinaryString(long n) {


StringBuffer bitPattern = new StringBuffer(67);
for (int i = 63; i >= 0; i--) {
bitPattern.append((n & BIT_MASK[i]) == 0 ? '0' : '1');
if (i % 8 == 0) bitPattern.append(" ");
}
return bitPattern.toString();
}

The bold line of code appends a space to the bitPattern string buffer every
8th iteration of the loop.
The remainder operator should not be used to determine if a number is even
of odd. Nevertheless, % 2 == 0 or % 2 != 0 (or % 2 == 1) are sometimes used
to test for even and odd numbers, respectively. As discussed in the last section,
& 1 == 0 or & 1 != 0 are also used for this purpose. The only difference is that
& 1 is significantly faster than % 2.
There are other remainder methods in the core API, including IEEE-
remainder(double f1, double f2) in the java.lang.Math class
and remainder(BigInteger val) in BigInteger. The former rounds

EXPRESSIONS, STATEMENTS, AND BLOCKS 449


instead of truncates and the latter is the BigInteger equivalent of the remain-
der operator.

NOTE 4.2
The AND, inclusive OR, and exclusive OR operators (&, |, and ^), are re-
ferred to as either Boolean logical operators or integer bitwise op-
erators in the JLS depending on the type of the operands. The Boolean
logical operators are rarely, if ever, used. They are primarily discussed
in the next section. The integer bitwise operators are introduced in
4.5.6 Bitwise Operators &, |, ^, >>, >>>, and <<, but their practical
significance may not be appreciated until reading 4.7 A Bitwise Primer.

4.5.5 Boolean Logical Operators &&, ||, &, |, and ^


There are five operators discussed in the section. Three are referred to as
Boolean logical operators in the JLS. Those are the AND, inclusive-OR, and
exclusive-OR operators in Table 4.8. The other two are the conditional-and and

Table 4.8 Boolean Logical Operators


Unconditional
Conditional Operatora Operator Description

&& conditional-and & AND Evaluates to true if both the left- and
right-hand operands are true .
Otherwise, evaluates to false.

450 JAVA RULES


Table 4.8 Boolean Logical Operators
Unconditional
Conditional Operatora Operator Description

|| conditional-or | Inclusive Evaluates to true if either the left- or


OR right-hand operands are true,
including when both operands are
true. Otherwise, evaluates to false.
Note that the inclusive OR operator acts
like “either…or” in an English sentence.

^ Exclusive Evaluates to true if either the left- or


OR right-hand operands are true,
excluding when both operands are
true. Otherwise, evaluates to false.
a. Unlike the AND, inclusive OR, and exclusive OR, which are not hyphenated and are capitalized, the names
of the conditional-and and conditional-or operators are hyphenated and not capitalized. Surprisingly, AND and
OR can be found in Merriam-Webster's Collegiate Dictionary online (www.m-w.com), and they are capital-
ized.

conditional-or operators. Discussing these operators separately has always


struck me as odd. The conditional operators && and || are the same as & and
| operators except that the right-hand operand is conditionally evaluated. The
conditional-and operator will not evaluate the right-hand unless the left-hand
operand evaluates to true. The conditional-or operator will not evaluate the
right-hand unless the left-hand operand evaluates to false. The conditional-and
operator is sometimes used to guard against the possibility of an exception
being thrown. Here is an example of some code you may find in a main method:

if ((args.lenth > 0) && (args[0].equals("whatever")))


If args.length is not greater than zero, no command-line arguments have


been passed and invoking args[0].equals("whatever") will throw an
ArrayIndexOutOfBoundsException.
The preponderance of the conditional-and and conditional-or operators in
code (versus the & and | operators) is attributable to the fact that they are sim-
ply more efficient. Why continue to evaluate the operands in a Boolean control

EXPRESSIONS, STATEMENTS, AND BLOCKS 451


flow expression when the value of the expression is already known? Here are
some if statements from the core API that make no sense.

if ((firstColon > 0) & (secondColon > 0) &


(secondColon < s.length()-1)) { …

if ((firstDash > 0) & (secondDash > 0) &


(secondDash < date_s.length()-1)) { …

if ((period > 0) & (period < time_s.length()-1)) { …

If firstColon, firstDash , or period is not greater than zero, the Bool-


ean control flow expression evaluates to false and there is no reason to con-
tinue evaluating the other operands. The conditional-and and conditional-or
operators should always used in control flow expressions. The alternative of
using the & or | to make sure a right-hand operand with side-effects is always
evaluated is almost universally frowned upon. The problem is that another pro-
grammer may change to the more common && and || operators (either deliber-
ately or inadvertently) without realizing that the right-hand operand has important
side effects.
While I think the descriptions in Table 4.8 is adequate, the truth tables in Fig-
ure 4.2 are included for the sake of completeness. When reading these tables,
the first column is the left-hand operand, the second column is the right-hand
operand, and the third column is the result.

AND
false false false
false true false
true false false
true true true

Inclusive OR
false false false
false true true
true false true

452 JAVA RULES


true true true

Exclusive OR
false false false
false true true
true false true
true true false

Figure 4.2 Truth tables for Boolean logical operators

As stated above, the AND, inclusive OR, and exclusive OR operators seldom
if ever have boolean type operands. There is only one programmer convention
of which I am aware that uses any of these Boolean logical operators. It is an
alternative to using the ?: operator to invoke equals(Object obj) without
the possibility of throwing a NullPointerException. For example,

if ((factory == null) ^ (other.factory == null))


return false;
if (factory != null) {
if (!factory.equals(other.factory))
return false;
}

Presumably this is being done because the ^ operator is faster than using the
?: operator. The idea has gained a significant foothold in the core API. Here is
yet another example:

if ((publicKey == null) ^ (identity.publicKey == null))


return false;
if (publicKey != null && identity.publicKey != null)
if (!publicKey.equals(identity.publicKey))
return false;

Most examples follow this general coding style, but at least one core API pro-
grammer uses the same coding technique as follows.

if (!(((csf == null) ^ (csfClient == null)) ||


((ssf == null) ^ (ssfClient == null)) ||

EXPRESSIONS, STATEMENTS, AND BLOCKS 453


((csf != null) && !(csf.equals(csfClient))) ||
((ssf != null) && !(ssf.equals(ssfClient))))) {

This particular example is not from an equals(Object obj) method, which


is part of the problem. Once you accept this in the equals(Object obj)
method it begins to be more widely used. Sure it works, but there is a much
more widely used programmer convention that accomplishes exactly the same
thing. For example,

if (!(o1==null ? o2==null : o1.equals(o2)))


return false;

Not only is this programmer convention clearly more readable, I have run a num-
ber of microbenchmark tests and using the ^ operator as shown in the core
API examples above is actually slower than using the ?: operator. Really!

4.5.6 Bitwise Operators &, |, ^, >>, >>>, and <<


There are six operators discussed in this section. Three are referred to as inte-
ger bitwise operators in the JLS. Those are the AND, inclusive-OR, and
exclusive-OR operators in Table 4.9. The other three are the shift operators.
Although the JLS does not refer to the shift operators as bitwise operators, they
are almost always used along with the integer bitwise operators. All of these
operators have something else in common. Their operands must evaluate to one
of the primitive integral types (which in this context includes the char data
type). Furthermore, the type of the expression in which they are used is always
either int of long. In Java Rules, both the integer bitwise operators and the
shift operator are regarded as bitwise operators.
While I think the descriptions in Table 4.9 are adequate, the truth tables in
Figure 4.3 are included for the sake of completeness. When reading these
tables, the first column is the left-hand operand, the second column is the right-
hand operand, and the third column is the result.

454 JAVA RULES


Table 4.9 Bitwise Operators
Operatora Description

& Bitwise Each bit evaluates to 1 if the corresponding bit in the left-hand and
AND right-hand operand is 1. Otherwise, the bit evaluates to 0. For
example,

00110101
& 00101100
00100100

| Bitwise Each bit evaluates to 1 if the corresponding bit in the left-hand or


Inclusive OR right-hand operand is 1, including when both bits are 1.
Otherwise, the bit evaluates to 0. Note that the inclusive OR
operator acts like “either…or” in an English sentence. For example,

00110101
| 00101100
00111101

^ Bitwise Each bit evaluates to 1 if the corresponding bit in the left-hand or


Exclusive OR right-hand operand is 1, excluding when both bits are 1.
Otherwise, the bit evaluates to 0. For example:
(a.k.a. XOR)

00110101
^ 00101100
00011001

>> Right shift Evaluates to the value of the left-hand operand right-shifted the shift
distance using sign extension. The shift distance is usually the value
of the right-hand operator, but not always. Shift distances are
discussed in detail below.

>>> Unsigned Evaluates to the value of the left-hand operand right-shifted the shift
Right shift distance using zero extension.

<< Left shift Evaluates to the value of the left-hand operand left-shifted the shift
distance. Low-order bits are zero filled.

EXPRESSIONS, STATEMENTS, AND BLOCKS 455


a. If the operands of a AND, inclusive OR, or exclusive OR operator are integers (versus boolean), the JLS con-
sistently refers to the operators as bitwise AND, bitwise inclusive OR, and bitwise exclusive OR. I do so only
in this table.

Inclusive Exclusive
AND OR OR
0 0 0 0 0 0 0 0 0
0 1 0 0 1 1 0 1 1
1 0 0 1 0 1 1 0 1
1 1 1 1 1 1 1 1 0

Figure 4.3 Truth tables for integer bitwise operators

Note that the descriptions of the shift operators in Table 4.9 do not mention
multiplying and dividing by powers of two, as explained in the following quote
from the JLS.
The value of n<<s is n left-shifted s bit positions; this is equivalent
(even if overflow occurs) to multiplication by two to the power s.

The value of n>>s is n right-shifted s bit positions with sign-extension.


The resulting value is n/2s. For nonnegative values of n, this is equiva-
lent to truncating integer division, as computed by the integer division
operator /, by two to the power s.26

While this is certainly mathematically true, most Java books make way too much
of this. It is actually done a lot less frequently than you may imagine. Here is a
rare example from a simplified version of the write(int b) method in
java.io.ByteArrayOutputStream , which writes a single byte to the
output stream:

public synchronized void write(int b) {


int newcount = count + 1;
if (newcount > buf.length) {
byte newbuf[] = new byte[Math.max(buf.length << 1,

26. Gosling et al., §15.19, “Shift Operators.”

456 JAVA RULES


newcount)];
System.arraycopy(buf, 0, newbuf, 0, count);
buf = newbuf;
}
buf[count] = (byte)b;
count = newcount;
}

The count is the number of characters in the buffer, which must not exceed the
buffer size. If the buffer is already full, this method increases the buffer size by
two times the current size or the current buffer size plus one, whichever is
greater. Note that newcount will never be greater than buf.length << 1
unless buf.length is zero (which is possible given the constructor design) or
buf.length << 1 results in overflow. This is a questionable implementation.
Most of the time when the shift operators are used to multiply or divide the
right-hand operand is 1 (as in this example), which means that the left-hand oper-
and is being multiplied or divided by two. While this is marginally more efficient
than using the * and / operators, it is so seldom done that I think mainstream
business application programmers should avoid it altogether. It is definitely not
an easy read because of the shift distance. For example, i << 3 means i * 4,
not i * 3. Note also that using the left-shift operator to divide by powers
of two only works for positive numbers. Of course, left and right shifting is
always multiplying and dividing by powers of two; this is a question of intent. For
example, I regard the initialization of bit masks such as MAXIMUM_CAPACITY
= 1 << 30 as purely a shift operation. Others may well regard it as multiplica-
tion. If you want to know how the shift operators are used “in the real world” read
4.7 A Bitwise Primer.
In shift operations, the left-hand operand is the value to be shifted, and the
right-hand operand is the shift distance. The operands of a shift expression
are promoted separately using unary numeric promotion. The type of a
shift expression is the promoted type of the left-hand operand, which is always
either int or long. Shift distances are usually expressed as numeric literals
(4, 8, 16, 24, 32, etc.), but a variable is occasionally used. It should be under-
stood that shifting more than 31 bits in an int is meaningless. Likewise, shift-
ing a long more than 63 bits is meaningless. Thus the ishl, ishr, lshl,

EXPRESSIONS, STATEMENTS, AND BLOCKS 457


and lshr machine instructions (you can probably guess their meaning) effec-
tively limit the value of the shift distance depending on the promoted type of the
left-hand operand. This is a “robust” means of limiting shift distances to an
appropriate range of values. The alternative would be to throw a runtime excep-
tion such as Invalid-ShiftDistance. This behavior is explained as fol-
lows in the JLS:
If the promoted type of the left-hand operand is int, only the five low-
est-order bits of the right-hand operand are used as the shift distance.
It is as if the right-hand operand were subjected to a bitwise logical
AND operator & with the mask value 0x1f. The shift distance actually
used is therefore always in the range 0 to 31, inclusive.

If the promoted type of the left-hand operand is long, then only the six
lowest-order bits of the right-hand operand are used as the shift dis-
tance. It is as if the right-hand operand were subjected to a bitwise log-
ical AND operator & with the mask value 0x3f. The shift distance
actually used is therefore always in the range 0 to 63, inclusive.27

The following program demonstrates this behavior using a 0x1f mask (as if left
or right shifting an int).

class Test {
public static void main(String[] args) {
//just enough times to show integer overflow
for (int distance=0; distance < 40; distance++) {
System.out.println(BitPattern.toBinaryString
(distance & 0x1f)); //mask for ints
}
}
}

Executing this program prints

00000000 00000000 00000000 00000000


00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000010
00000000 00000000 00000000 00000011
00000000 00000000 00000000 00000100

27. Ibid.

458 JAVA RULES


00000000 00000000 00000000 00000101
00000000 00000000 00000000 00000110
00000000 00000000 00000000 00000111
00000000 00000000 00000000 00001000
00000000 00000000 00000000 00001001
00000000 00000000 00000000 00001010
00000000 00000000 00000000 00001011
00000000 00000000 00000000 00001100
00000000 00000000 00000000 00001101
00000000 00000000 00000000 00001110
00000000 00000000 00000000 00001111
00000000 00000000 00000000 00010000
00000000 00000000 00000000 00010001
00000000 00000000 00000000 00010010
00000000 00000000 00000000 00010011
00000000 00000000 00000000 00010100
00000000 00000000 00000000 00010101
00000000 00000000 00000000 00010110
00000000 00000000 00000000 00010111
00000000 00000000 00000000 00011000
00000000 00000000 00000000 00011001
00000000 00000000 00000000 00011010
00000000 00000000 00000000 00011011
00000000 00000000 00000000 00011100
00000000 00000000 00000000 00011101
00000000 00000000 00000000 00011110
00000000 00000000 00000000 00011111
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000010
00000000 00000000 00000000 00000011
00000000 00000000 00000000 00000100
00000000 00000000 00000000 00000101
00000000 00000000 00000000 00000110
00000000 00000000 00000000 00000111

There are two details to notice; the first is how 11111 (the binary value of
0x1f) is the largest number that can be expressed by the parenthesized
expression (distance & 0x1f) . The other is that this is a special case of
integer overflow.
The unsigned right-shift operator is a new operator that does not exist in the
C and C++ programming languages. The rationale for a new shift operator is

EXPRESSIONS, STATEMENTS, AND BLOCKS 459


backward compatibility with some implementations of those languages. If the
left-hand operand (the value to be shifted) is a signed integer with a negative
value, the behavior of the right-shift operator >> in those languages is imple-
mentation defined. The unsigned right-shift operator >>> in the Java program-
ming language is consistent with those implementations of C and C++ that do
not use sign extension.
In practice, the signed and unsigned right shift operators are most often
used interchangeably as conveyor belts at the end of which bits are processed
in groups of four or eight. Sign extension versus zero filling is a moot point
because the “conveyor belt” effectively stops after the last bit is processed (bit
31 or 63). Thus the use of the signed or unsigned right shift operator becomes
a matter of style (my preference being the unsigned right shift operator). There
are times when the use of the unsigned right-shift operator is absolutely neces-
sary, but that is not usually the case. I like the conveyor belt analogy because
while it is easy to understand the specification for the bitwise operators, under-
standing how they are used can be really intimidating. In the remainder of this
section I will discuss how the &, |, and ^ operators are used, but this material is
only introductory. The practical uses of bitwise operators (including the shift
operators) is the subject of 4.7 A Bitwise Primer. The difference between the
right-shift and unsigned right-shift operators is discussed in detail in 4.7.4
Unsigned Bytes beginning on page 512. That section also includes three exam-
ples of when using the unsigned right-shift operator is required (i.e., not a matter
of style).
The AND operator has two clearly identifiable uses. The first is to limit the
value of the expression in which it is used to within some range. That range is
zero and some value that—if expressed using binary notation—would be a
sequence of consecutive bits set to one. This explains why hexadecimal literals
are almost exclusively used to express the upper bound. A hexadecimal literal
always represents a sequence of consecutive bits if all of the digits
except the leftmost are f (or F). For example, the bit mask 0x1f used in the
previous shift distance example is 11111 , and the 0xffff used below is
1111111111111111 . The fact that the right-hand operand is the bound and is
expressed as a hexadecimal literal, however, is merely a programmer conven-

460 JAVA RULES


tion that makes such expressions much easier to read. Integer literals can be
used, but they are very problematic. For example, the integer literal 3—if
expressed using binary notation—would be a sequence of consecutive bits set
to one, but not 4. For example,

import java.util.Random;
class Test {
public static void main(String[] args) {
Random random = new Random();
for (int i=0; i < 10; i++) {
System.out.print(random.nextInt() & 3);
System.out.print(" ");
System.out.println(random.nextInt() & 4);
}
}
}

Here is the output from one execution:

2 0
1 4
2 4
2 4
2 4
3 4
1 4
3 4
1 0
0 0

As you can see, if the bits are not consecutive, this programming technic does
not work. That is why hexadecimal literals should always be used.
You have already seen this use of the AND operator in the shift distance
example. Here is a more elaborate example from the java.io package:

private static int counter = -1;

private static File generateFile(String prefix,


String suffix,
File dir)
throws IOException
{

EXPRESSIONS, STATEMENTS, AND BLOCKS 461


if (counter == -1) {
counter = new Random().nextInt() & 0xffff;
}
counter++;
return new File(dir, prefix + Integer.toString(counter) +
suffix);
}

This code is used by the public createTempFile(String prefix,


String suffix, File directory) method, and results in temporary file
names such as tmp58713.txt where tmp is the prefix argument passed,
58713 is the random number, and .txt is the suffix argument passed (oth-
erwise known as a file extension). This programming technique is safe for gener-
ating unique file names, but is otherwise no longer used. The nextInt(int
n) method in the Random class should always be used to generate random
numbers within some range. Note that there is a substantial difference, however.
The n argument when invoking the nextInt(int n) method is exclusive.
The other, much more common use of the AND operator is to mask bits. The
same programmer convention is used. The left-hand value is the bit set to be
masked (typically either an int or long). The right-hand operand is the mask,
which again is usually expressed as a hexadecimal literal. (Of course, these are
matters of intent. Is the 0xffff in the previous example an upper bound or a
bit mask?) Occasionally, the decimal literals 1, 2, or 3 are used to mask bit 0, bit
1, or bits 0 and 1, respectively. For example,

long bitset = 0;
final int LONG_BITS = 64;
int[] counter = new int[LONG_BITS];

for (int i=0; i<LONG_BITS; i++) {
// If no (more) bits are set, break out of the loop
if (bitset == 0) {
break;
}
if ((bitset & 1) != 0) { //uses an integer literal as a mask
counter[i]++;
}
bitset >>>= 1;
}

462 JAVA RULES


If any of the bits in the bitset variable is set to 1, this loop increments the cor-
responding counter in the counter array. The loop control variable i is equal
to the bit number (starting with zero and ending with bit 63).
Most of the time, however, masks are either hexadecimal literals or con-
stants initialized with hexadecimal literals. The overwhelming majority of masks
are one of the following.
• Bit Masks: These are usually int or long type constants initialized
with a power of two (thus “masking” a single bit). Assigning a power of
two to a static final int field is a way of naming individual bits of
data. There are some examples in the next bulleted item.
• Combined Bit Masks: Individual bit masks are combined using the inclu-
sive OR operator. For example, the following if statement from the core
API uses what I like to call a dynamically combined bit mask.
if ((infoflags & (ERROR | ABORT)) != 0)

If either the ERROR or ABORT bit is set in infoflags this if state-


ment evaluates to true. Unlike individual bit masks (which are almost
always named) dynamically combined bit masks are just as common as stat-
ically combined bit masks. They do not have names and last only as long as
the expression in which they are used. The following example of a statically
combined bit mask is from the FilePermission class.
private final static int NONE = 0x0;
private final static int EXECUTE = 0x1;
private final static int WRITE = 0x2;
private final static int READ = 0x4;
private final static int DELETE = 0x8;
private final static int ALL = READ|WRITE|EXECUTE|DELETE;

These are sometimes referred to as sets, but not in Java Rules. For rea-
sons explained in the note at the start of 4.7.1 Bits on page 477, I typically
name the int or long in which bits are stored bitset (instead of
flags). The same term cannot be used to describe both of the operands
in a bitwise operation. Thus I use the term combined bit mask instead set to
describe bit masks such as ALL.
• Nybble mask: This is either 0xf or 0xF. Used when converting nybbles to
hexadecimal digits.

EXPRESSIONS, STATEMENTS, AND BLOCKS 463


• Byte Mask: This is either 0xff or 0xFF. There are many uses of byte
masks. Perhaps the most common is reading and writing binary data.
Masks are used pretty much as the name implies. They mask off one or more
bits in the other operand. For example,

if ((ch & 0xff) == ch)

Assuming that the type of ch is char, can you tell what this code is asking?
This if statement tests to see if ch is in the range of an ASCII or Latin-1 charac-
ter. If any of the bits in the high-order byte were set, the expression ch & 0xff
would evaluate to false.
Assuming that the same bit is not set in both operands, the | operator is to
bits what the + operator is to numbers. For example,

public class Test {


public static void main(String[] args) throws IOException {
System.out.println(1 | 2 | 4 | 8 | 16 | 32 | 64);
System.out.println(1 + 2 + 4 + 8 + 16 + 32 + 64);
System.out.println(2 | 2);
System.out.println(2 + 2);
}
}

Executing this program prints

255
255
2
4

The values added in the first two lines of code are all unique non-zero powers of
two; hence, the results are equal. When adding two plus two, however, the
results are different because the | operator does not “carry” as does the +
operator. Thus one thinks of the inclusive OR operator as “combining” (unique)
bits rather than “adding” them.
The ^ operator (exclusive OR) is the least intuitive of the bitwise operators
because if both bits are 1, the result is 0. Note that ^ is frequently used in the
API docs for exponentiation. For example, 2^32 is read as “two raised to the
power of 32.” This use of ^ has nothing to do with the exclusive-OR opera-

464 JAVA RULES


tor. The exclusive-OR operator is by far the least used of any operator in the
Java programming language.

The ^ operator is rarely used outside of hashCode() methods as


either an integer bitwise operator or a Boolean logical operator.

This includes classes in the java.math package, java.lang.Float-


ingDecimal (a package-private class used to print floating-point values),
encryption classes, and others that use the other bitwise operators to do highly
sophisticated math. In hashCode() methods, the ^ operator is used to com-
bine hash codes or other values. For example,

hashcode = language.hashCode() ^
country.hashCode() ^
variant.hashCode();

This is the code used to compute a hash code for the Locale class. Even as
an operator for individual bits (or bit flags), the exclusive OR operator is used to
toggle a bit from one to zero or vice versa depending of the setting of the bit
prior to the operation. In practice, however, bits are seldom toggled.

4.5.7 Ternary Conditional Operator ?:


The conditional operator ?: is the only ternary operator in the Java program-
ming language. The general form is as follows.

BooleanExpression ? expression : expression


The first operand to the left of the question mark must be a Boolean expression
and is comparable to the Boolean expression in an if statement. The other two
operands can be any matching data type. That is to say, both of the other two
operands must be primitive numeric types, the boolean type, or reference
types. Otherwise, the conditional expression will not compile. In the case of ref-
erences types, either or both of the second and third operands can also evaluate
to null.
Determining the type of a conditional expression is unlike any other expres-
sion in the Java programming language. As would be expected it depends on the

EXPRESSIONS, STATEMENTS, AND BLOCKS 465


type of the second and third operands. The following set of rules from the JLS
are used in determining the type of an expression in which this operator is used.
• If the second and third operands have the same type (which may be the null
type), then that is the type of the conditional expression.
• Otherwise, if the second and third operands have numeric type, then there
are several cases:
° If one of the operands is of type byte and the other is of
type short, then the type of the conditional expression is
short .
° If one of the operands is of type T where T is byte,
short , or char, and the other operand is a constant
expression of type int whose value is representable in type
T, then the type of the conditional expression is T.
° Otherwise, binary numeric promotion is applied to the oper-
and types, and the type of the conditional expression is the
promoted type of the second and third operands. Note that
binary numeric promotion performs value set conversion.
• If one of the second and third operands is of the null type and the type of the
other is a reference type, then the type of the conditional expression is that
reference type.
• If the second and third operands are of different reference types, then it
must be possible to convert one of the types to the other type (call this lat-
ter type T) by assignment conversion; the type of the conditional expression
is T. It is a compile-time error if neither type is assignment compatible with
the other type. 28
This is about as complicated as it gets when it come to determining the type of
an expression.
The Boolean expression is evaluated first. The value of the conditional
expression depends upon which of the two remaining operand expressions is
evaluated. Only one is evaluated. The second operand expression (to the left of
the colon) is evaluated if the Boolean expression is true. The third operand
expression (to the right of the colon) is evaluated if the Boolean expression is

28. Gosling et al., §15.25, “Conditional Operator ? :.”

466 JAVA RULES


false. This should be easy to remember because it is the same true first
false second binary branching order of if-then-else statements.
The JLS name for this operator is conditional operator.29 No one appar-
ently likes this name, however, because there are so many alternatives. Here is
a list of some of the alternative names that are being used in other Java books:
• if-else operator
• question/colon operator
• ternary operator
• ternary if-else operator
• the triadic operator
I am not crazy about conditional operator either, but none of these are any
better. You can avoid the ambiguity of the “conditional operator” name by refer-
ring to it as the ternary conditional operator (as I do in the name of this sec-
tion).
The conditional operator is controversial; some programmers like it, some
do not. Because of this, you should avoid being overly creative in the use of
the conditional operator. For example, I would not use it as an argument
expression (though some programmers do). There are three uses that should
never be questioned:
• As the right-hand operand of a simple assignment expression. For example,
int sign = ((valBits >> 63)==0 ? 1 : -1);

This line of code is from BigDecimal. The outermost parentheses are


not required, but are understandable as a matter of style.
• In a return statement. For example,
public static int abs(int i) {
return (i < 0) ? -i : i;
}

This is one possible implementation of the Math.abs(int i) method. It


could have been written using an if-then-else statement such as the fol-
lowing.

29. Ibid.

EXPRESSIONS, STATEMENTS, AND BLOCKS 467


public static int abs(int i) {
if (i < 0)
return –i;
else
return i;
}

In this case, the desirability of a single return statement dictates the


use of the conditional operator. One of three values can be returned by
compounding the conditional operator:
return type == KEYS ? e.key : (type == VALUES ? e.value : e);

This example from the core API returns a key, value, or a Map.Entry.
• As a guard against the possibility of a NullPointerException being
thrown when invoking an instance method. For example,
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;

This is a well established programmer convention frequently used in


equals(Object obj) method implementations. Here is another example
from the hashCode() method in a Map.Entry implementation:
(e.getKey()==null ? 0 : e.getKey().hashCode()) ^
(e.getValue()==null ? 0 : e.getValue().hashCode())

These three well established uses easily account for the overwhelming majority
of all conditional operators.
As suggested in the second bulleted item, the conditional operator is often
thought of as an alternative to if-then-else statements for implementing
binary branches (witness “if-else operator” and “ternary if-else operator” from the
list of alternative names above). This is not really true. Conditional expressions
are just that, — expressions. They are not statements; nor can they be used as
expression statements. For example,

class Test {
public static void main(String[] args) {
boolean b = true;
b ? System.out.println("true") :
System.out.println("false");

468 JAVA RULES


}
}

Attempting to compile this program generates the following compiler error:

Test.java:4: not a statement


b ? System.out.println("true") :
^
1 error

The reason why conditional expressions cannot be used as top-level expressions


(in expression statements or the ForUpdate part of a for loop header) is that
they do not always have side effects. Furthermore, conditional operators condi-
tionally evaluate one of two expressions, whereas if-then- else statements
execute either a statement or block. For example,

if (n == 0)
value = false;
else if (n == 1)
value = true;
else
assert false;

This if-then-else statement cannot be replaced by conditional operators. Here


is an attempt to do so:

value = n == 0 ? false : (n == 1 ? true : assert false);

This line of code generates all sorts of compiler errors because assert
false is a statement.
When there is actually a choice between using the conditional operator and
an if-then-else statement, preference should be given to the conditional oper-
ator because it is much more compact and does not suffer from the mainte-
nance problems that can occur when braces are not used in if-then-else
statements.
The “Code Conventions for the Java Programming Language” document
says that if a binary operator is used in the first expression (to the left of the ?)
in a ?: operator, parentheses should be used.30 For example,

30. Unascribed, §10.5.3, “Expressions before `?' in the Conditional Operator.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 469


(x >= 0) ? x : -x;

This is a useful programmer convention. Without the use of parentheses, such


an expression would be difficult to read. There is an exception, however. When
the binary operator is == parentheses are often not used.

4.5.8 The Simple and Compound Assignment Operators


As shown in Table 4.10 there are a total of twelve assignment operators, one
simple and eleven compound. Here “assignment” means storing the value of the

Table 4.10 The Simple and Compound Assignment Operators


Assignment
Operand Expression
Operator Type Type

= Simple The type of the right- The declared type of the


assignment hand operand must be variable denoted by the left-
operator assignment compatible hand operand
with the declared type
of the variable denoted
by the left-hand operand

+= -= Compound Primitive numeric types, The promoted type of the


*= /= arithmetic unless one of the operands using binary
operators operands of the += numeric promotion
%=
operator is a string

<<= Compound Primitive integral types The promoted type of the


>>= shift (which in this context left-hand operand (the value
operators includes the char to be shifted) using unary
>>>=
data type) numeric promotion

^= &= Compound Either one of the Always either int or


|= bitwise primitive integral types long (the promoted type
operators (which in this context of the operands using
includes the char binary numeric promotion)
data type) or both for integer bitwise
operands must be the operations, else
boolean type boolean

470 JAVA RULES


right-hand operand in the variable denoted by the left-hand operand. If the assign-
ment statement compiles without the use of a cast operator, then the types are
said to be assignment compatible. The value of the right-hand operand is
implicitly converted to the declared type of the variable denoted by the left-hand
operand as part of the assignment operation. See also 5.7.1 Simple Assignment
Conversion Context.
An assignment expression can be used anywhere a variable can be used.
Think of the prefix increment and decrement operators. Are they not a means of
assigning a different value to a variable immediately before it is used in some
larger context? Assignment expressions can be thought of in the same way. The
“Code Conventions for the Java Programming Language” document refers to
these as embedded assignments and discourages their use:
Do not use embedded assignments in an attempt to improve run-time
performance. This is the job of the compiler. Example:

d = (a = b + c) + r; // AVOID!

should be written as

a = b + c;
d = a + r;31

This is contrary to established coding practice, however. For example,

class Test {
public static void main(String[] args)
throws java.io.IOException {
int i;
System.out.println("PRESS CONTROL Z (^Z) TO END PROGRAM");
while ((i = System.in.read()) != -1) {
char c = (char) i;
if (c=='\n' || c=='\r')
continue;
System.out.println(c + " = ASCII " + i);
}
}
}

31. Unascribed, §10.5.1, “10.4 Variable Assignments.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 471


I am quite sure there are other examples in which embedded assignment make
for compact yet easy to read and understand code.
Compound assignment operators such as += are commonly thought of as
the equivalent of the compounded operations. That is why the compound assign-
ment operators are frequently described as “short cuts.” For example, the
assignment expression a = a + b is supposed to be the same as a += b.
Expressions that use compound assignment operators are not always the equiv-
alent, however, because the definition of a compound assignment operator
includes an “implicit cast.” For example:

int i = Math.PI; //COMPILER ERROR

This simple assignment expression does not compile. The type of the right-hand
operand expression Math.PI is double. A conversion from double to int
obviously can result in a loss of data. Now consider the same expression written
using a compound assignment operator:

int i = 0;
i += Math.PI;
System.out.println(i);

The diagnostic message prints 3. The implicit cast in a compound assignment


operation is discussed at length in 5.7.5 The Implicit Cast in a Compound
Assignment Operation.

NOTE 4.3
However unconventional it may sound, the instanceof and cast op-
erators should always be discussed together because at the machine
instruction level they are practically the same. The only substantial
difference is that the instanceof operator returns false whereas
a cast operator throws a ClassCastException. Therefore 5.7.4
The Cast Operator should be read at the same time as this section.

472 JAVA RULES


4.6 The instanceof Type Comparison Operator
The formal name of the instanceof operator is the type comparison opera-
tor. The left-hand operand must evaluate to either a reference type or null. The
right-hand operand must be a class or interface type name. The instanceof
operator is a run-time assignment compatibility check, the details of which are
discussed in 5.7.4 The Cast Operator. However, the compiler checks to make sure
that it is at least possible for the object referenced by the left-hand operand to be
an instanceof the type named in the right-hand operand. If that is not possible,
a compiler error is generated. For example,

class Test {
public static void main(String[] args) {
String s = null;
if (s instanceof StringBuffer) //compiler error
System.out.println("does not print");
}
}

Attempting to compile this program generates the following compiler error:

Test.java:4: inconvertible types


found : java.lang.String
required: java.lang.StringBuffer
if (s instanceof StringBuffer) //compiler error
^
1 error

A variable of type String cannot reference an instanceof the String-


Buffer class because these are not even related classes. Assignment com-
patibility is discussed at length in the next chapter.
Be careful using this operator. It is not the same as asking the class of an
object. The class of an object is returned by the getClass() method in the
Object class. The difference is nowhere more pronounced than in implementa-
tions of the equals(Object obj) method. For example,

if (!(o instanceof Widget))


return false;

EXPRESSIONS, STATEMENTS, AND BLOCKS 473


This use of the instanceof operator is with the meaning “the class of the
object being compared must be this class,” but that is not what this code says.
It says o must be an instanceof Widget, which includes Widget sub-
classes. These lines of code directly violate the symmetry clause of the
equals(Object obj) method contract in extensible classes. In Effective
Java, a runaway best seller, Bloch erroneously concludes that the symmetry
problem cannot be solved.
…this is a fundamental problem of equivalence relations in object-ori-
ented languages. There is simply no way to extend an instantiable
class and add an aspect while preserving the equals contract.32

This simply is not true. Dr. Mark Davis solved this problem years ago. His solu-
tion is to not use the instanceof operator where the meaning is “the class of
this object must be this class.” Here is Bloch’s example with the solution sug-
gested by Dr. Davis.

import java.awt.Color;
class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object obj) {
if (o == null) return false;
if (obj.getClass() != this.getClass())
return false;
Point p = (Point)obj;
return p.x == x && p.y == y;
}
}
class ColorPoint extends Point{
private Color color;

public ColorPoint(int x, int y, Color color) {


super(x,y);

32. Joshua Bloch, Effective Java Programming Language Guide, (Boston: Addison-Wesley,
2001), “Item 7: Obey the general contract when overriding equals.”

474 JAVA RULES


this.color = color;
}

public boolean equals(Object obj) {


if (!super.equals(obj))
return false;
ColorPoint cp = (ColorPoint)obj;
return cp.color == color;
}
}

See 4.11.3.2 The equals(Object o) Method in Volume 1 for a complete


discussion of this subject.

NOTE 4.4
There is a common thread that runs through all of the bitwise primer
subsections. They all discuss what I refer to as conceptual data
types, which are bits, nybbles, and unsigned bytes. There are no type
names that can be used to allocate a bit, nybble, or unsigned byte.
They are allocated as one of the integral data types byte, short,
char, int, or long (usually int because that is the smaller of the
two computational types). Conceptual data types are made possible by
the use of the bitwise operators.

4.7 A Bitwise Primer


If you are not already a bitwise programmer, this section is an introduction
into the world of bit processing. The technical specifications for the bitwise oper-
ators are given in 4.5.6 Bitwise Operators &, |, ^, >>, >>>, and <<. This sec-
tion explores how those operators are used in the core API. All of the most
common uses are covered. If you are already a bitwise programmer, the follow-
ing subsections should still prove an interesting study of programmer conven-
tions in this area. While writing this section I studied hundreds of uses of the
bitwise operators in the core API. In fact, I looked at every single use of the bit-

EXPRESSIONS, STATEMENTS, AND BLOCKS 475


wise operators in both the core API and the sun or com.sun packages in the
most recent J2SE.
As a mainstream business application programmer, you may never have to
use some of the programming techniques discussed in the following sections
(such as converting nybbles to hexadecimal digits). Every Java programmer,
however, should at least be able to read code that uses these programming

The bitwise operators are an assembler-like language within a


language. I suspect that many Java programmers earn a comfort-
able living without a detailed knowledge of how they are used.

techniques. The point here is to understand the bitwise operators at a profes-


sional level by studying how they are used to solve some very common program-
ming problems.
Rest assured that the core API includes examples of code that uses the bit-
wise operators in ways that are so obscure and difficult to understand that they
easily rival the best intentionally obfuscated C code, but such examples are truly
rare in the Java programming language (reflecting what I think is not only a
change in programming languages but also in the culture of programming
towards a better appreciation of simplicity as a design goal). There are no such
examples in this bitwise primer. All of the examples are easy to read and thor-
oughly explained.
One of the heaviest uses of bitwise operators is in hashCode() method
implementations. The hashCode() method is not discussed in this chapter
because it is a housekeeping method. The five housekeeping methods are dis-
cussed at the bottom of Chapter 4 of Volume 1. In the First Edition of Volume 1,
however, I do not discuss how to use the bitwise operators in a hashCode()
method; instead I advocate the use of a HashCode class. As it turns out, I did
not give any thought to the performance penalty of using such a class. I have
come to regard the proposed HashCode class as a gross error. Even main-
stream business application programers should know how to use the bitwise
operators to implement highly efficient hashCode() methods. Should Java
Rules survive to a Second Edition of Volume 1, I will elaborate on how to do so.

476 JAVA RULES


As a final note, programmer conventions are strongly emphasized in the fol-
lowing subsections. There needs to be a healthy respect for programmer
conventions when using the bitwise operators. They are indeed “an assem-
bler-like language within a language.” More so than other programming “lan-
guages,” bitwise programming is founded on programmer conventions. (While
writing this I realized the following note could not be more contrary to what I just
said. My point is not to discourage innovation. What I am trying to address are a
few examples I found while studying the use of bitwise operators in the core API
that appear to ignore programmer conventions merely for the sake of being dif-
ferent.)

NOTE 4.5
The next section uses some nonstandard terminology to refer to the
the int or long in which bits are allocated. That field is often named
flags, and individual bits are more generally referred to as bit flags.
Instead of referring to such fields as flags, I have expropriated the term
bitset from the BitSet class in java.util (which is too slow to
be used by serious bitwise programmers). Likewise, instead of refer-
ring to individual bits as flags, I simply refer to them as bits. Besides
being easier to read, this terminological shift was more or less forced
on all of us by the name of the BitSet class in the core API.

4.7.1 Bits
The term bit is short for binary digit. The bits in a integral data type are num-
bered from right-to-left beginning at zero. Thus bit 31 is the sign bit in an int or
float. In a long or double, bit 63 is the sign bit. A bit has one of two val-
ues, which is either 0 or 1. A bit is said to be set if its value is 1, and reset or
cleared if its value is 0.
Zero and one are binary values which gives rise to the question of boolean
versus bit. Bits are always an alternative to using boolean type variables, espe-

EXPRESSIONS, STATEMENTS, AND BLOCKS 477


cially when performance is at a premium. Surprisingly little is said on this subject.

It is important to appreciate that bit processing is not only a lan-


guage within the language, but an entirely different style of coding;
one that is extremely efficient both in terms of CPU cycles and mem-
ory usage.

It is largely a question of scale. If only one or two binary values need to be


stored, boolean type variables should be used. Otherwise code is what I can
only describe as overbearing (or perhaps overkill). Here is a questionable exam-
ple from the core API:

short changed = 0;
if( (flags & ImageObserver.HEIGHT) != 0 )
if( ! getElement().getAttributes().isDefined
(HTML.Attribute.HEIGHT) ) {
changed |= 1;
}
if( (flags & ImageObserver.WIDTH) != 0 )
if( ! getElement().getAttributes().isDefined
(HTML.Attribute.WIDTH) ) {
changed |= 2;
}
synchronized(this) {
if ((changed & 1) == 1) {
fWidth = width;
}
if ((changed & 2) == 2) {
fHeight = height;
}
if (loading) {
return true;
}
}
if( changed != 0 ) {

In my opinion, this code would read a lot easier had it used two boolean type
variables (perhaps widthChanged and heightChanged ) instead of a bit
set (the changed variable). If there are many binary values, however, using bits

478 JAVA RULES


can enhance performance and significantly reduce the memory footprint of an
application. As noted in 4.3.1 Numeric Promotion, however, object packing in
the HotSpot VM means that a boolean on the heap is now a byte instead of
an int, significantly reducing the amount of memory wasted when allocating a
boolean type variable.33 The computational type of a boolean is still int, but
that only effects the size of the constant pool and operand stacks (neither of
which are nearly as important as objects on the heap).
There are three clearly definable steps in using bits:
1. Allocate and initialize the bits
2. Declare bit mask constants
3. Use the integer bitwise operators
The remainder of this section discusses each of these three steps in order.
Bits are usually allocated as an int or long, but sometimes byte or
short is used (as in the previous example) if it is certain that only a few bits are
required. Here is an example of allocating 32 bits:

int bitset = 0; //all 32 bits are turned off

Such a variable is sometimes referred to as a vector of bits. I prefer the name


bit set. The bits in a bit set do not have to be related information. If they are not,
the generic bitset (or flags) variable name is commonly used. Otherwise,
the variable name may reflect the data stored in them, such as style from a
Font class example below.
There are 32 bits in an int. If more bits are needed, the bitset variable
can be declared as a long. If the bits are allocated as a long, however, so
should the bit masks; otherwise you run the risk of the following scenario.

class Test {
public static void main(String[] args) {
long bitset = 0;
final int WHATEVER = 1 << 31;

33. See Bug Id 4392283 which states that “Booleans are 1 byte fields instead of 4 bytes since the
object packing change was implemented.” Object packing is briefly discussed in a number of differ-
ent very similar HotSpot VM documents (for example, java.sun.com/products/hotspot/
docs/whitepaper/Java_HSpot_WP_v1.4_802_2.html).

EXPRESSIONS, STATEMENTS, AND BLOCKS 479


System.out.println(BitPattern.toBinaryString(bitset));
bitset |= WHATEVER; //binary numeric promotion
System.out.println(BitPattern.toBinaryString(bitset));
}
}

Executing this program prints

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000


11111111 11111111 11111111 11111111 10000000 00000000 00000000 00000000

The numeric promotion of WHATEVER resulted in 33 bits being set instead of


just one. Of course, this only happens when masking bit 31. Even if you do not
need 32 bits, declaring bitset as an int saves having to promote the
bitset variable every time it is used in bitwise operator expressions. Ideally,
the type of bit set and bit masks should be the same, either the int or long
computational type. Otherwise, binary numeric promotion makes using bits less
efficient.
The bits can be initially set to 0 by initializing the bit set variable with 0, or
they all can be set to 1 by using ~0. Any arbitrary number of low- or high-order
bits can be initialized to 1 (leaving the rest of the bits set to 0) by left shifting the
number of bits you want to set and then subtracting one from the result. For
example,

class Test {
public static void main(String[] args) {
int bitset = (1 << 16) - 1; //set 16 low-order bits
System.out.println(BitPattern.toBinaryString(bitset));
bitset = ~((1 << 16) - 1); //set 16 high-order bits
System.out.println(BitPattern.toBinaryString(bitset));
}
}

Executing this program prints

00000000 00000000 11111111 11111111


11111111 11111111 00000000 00000000

This is one programming technique that you will definitely want to add to you bag
of tricks. It is used a surprising number of times in the core API in a variety of dif-

480 JAVA RULES


ferent contexts. Remember to left shift the exact number of bits that you want
to set before subtracting one.
Bit masks are best initialized with hexadecimal literals. Here is an example
from java.lang.reflect.Modifier that uses leading zeros for the
sake of readability:

public static final int PUBLIC = 0x00000001;


public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;

I would not use the leading zeros as a matter of style unless there are a lot of bit
masks, as in this example. (When it comes to readability, “less is always better”
is my motto.) This repetition of 1, 2, 4, 8 is how to express powers of two in
hexadecimal literals. For example,

class Test {
public static void main(String[] args) {
System.out.println(0x1);
System.out.println(0x2);
System.out.println(0x4);
System.out.println(0x8);
System.out.println(0x10);
System.out.println(0x20);
System.out.println(0x40);
System.out.println(0x80);
System.out.println(0x100);
System.out.println(0x200);
System.out.println(0x400);
System.out.println(0x800);
}
}

Executing this program prints:

EXPRESSIONS, STATEMENTS, AND BLOCKS 481


1
2
4
8
16
32
64
128
256
512
1024
2048

Note that such bit masks are inlined constants, which means their value should
never be changed. The fact that bit masks are inlined constants significantly
adds to the overall efficiency of bit processing. Alternatively, the left-shift opera-
tor can be used to initialize bit masks. For example,

public static final int SHIFT_MASK = 1 << 0;


public static final int CTRL_MASK = 1 << 1;
public static final int META_MASK = 1 << 2;
public static final int ALT_MASK = 1 << 3;

Of course, the first bit mask could just as easily been initialized with one (the
expression 1 << 0 equals one). The << 0 is added as a matter of style. The pro-
grammer convention is to use hexadecimal literals because left-shifting becomes
awkward if there are a lot of bit masks.
Sometimes masks do not need a name and can be stored in an array in
which the index value corresponds to the bit mask you want to use. For example,
the BitPattern program uses a generic BIT_MASK array that is initialized
as follows:

static final int[] BIT_MASK = new int[32];


static {
for (int i = 0, length = BIT_MASK.length; i < length; i++)
BIT_MASK[i] = 1 << i;
}

These are unnamed bit masks, which are somewhat unusual. You can even set
the unnamed bit mask in a for statement header. For example,

482 JAVA RULES


for (int mask = 1; mask <= 0x80000000; mask <<= 1) {

}

Note that 0x80000000 corresponds to bit 31 (the last bit in an int).


Once the bitset variable and bit masks are declared, all that remains is to
use the bits in bitwise operator expressions. Table 4.11 describes the four oper-
ations that are basic to bit processing. When querying a bit, the choice between
> 0, != 0, and == MASK is a matter of style (though != 0 is the most widely
used in the core API), but only when querying an individual bit. When querying a
combined bit mask, > 0 and != 0 have an entirely different meaning than ==
MASK. The following program helps to illustrate the difference.

class Test {
public static void main(String[] args) {
int bitset = 0;
final int ONE = 0x1;
final int TWO = 0x2;
final int THREE = ONE | TWO;
bitset |= ONE;
if ((bitset & (ONE | TWO)) != 0)
System.out.println("at least one of the bits are set");
if (!((bitset & THREE) == THREE))
System.out.println("both bits are not set");
bitset |= TWO;
if ((bitset & THREE) == THREE)
System.out.println("both bits are now set");
}
}

Executing this program prints

at least one of the bits are set


both bits are not set
both bits are now set

EXPRESSIONS, STATEMENTS, AND BLOCKS 483


Table 4.11 The Four Basic Bit Processing Operations
Operation Operator Code Examples

Querying AND Allowing for stylistic differences, the following three if


statements are used to ask if an individual bit has been set.
Be careful, though, because the first two lines of code would
mean “are any of the bits set?” in a combined bit mask. The
third line of code must be used to ask if all of the bits in a
combined bit mask are set.a

if (bitset & MASK > 0)


if (bitset & MASK != 0)
if (bitset & MASK == MASK)

The following two if statements are used to ask if a bit has


been cleared. Be careful, though, because the first line of
code would mean “have all the bits been cleared?” in a
combined bit mask. The second line of code must be used to
a
ask if any of the bits have been cleared.

if (bitset & MASK == 0)


if (bitset & MASK != MASK)
The following if statement is used to ask if any bits not in a
mask are set. This query works equally well for a combined bit
mask.

if (bitset & ~MASK != 0)

Setting Inclusive Set this bit or all of the bits in a combined bit mask.
OR
bitset |= MASK;

Set all of the bits except this one. This query works equally
well for a combined bit mask.

bitset |= ~MASK;

484 JAVA RULES


Table 4.11 The Four Basic Bit Processing Operations
Operation Operator Code Examples

Clearing AND Clear this bit or all of the bits in a combined bit mask. Be
careful, though, because the bitwise complement operator (or
bit flipper) has exactly the opposite meaning it has when
querying, setting, or toggling.

bitset &= ~MASK;

Clear all of the bits except this one. This query works equally
well for a combined bit mask.

bitset &= MASK;

Toggling Exclusive Toggle this bit or all of the bits in a combined bit mask.
OR
bitset ^= MASK;
THE BIT
FLIPPER Toggle all of the bits except this one. This query works equally
at work well for a combined bit mask.

bitset ^= ~MASK;

a. Such a query would be very awkward for a dynamically combined bit mask because the bits would have to
be dynamically combined on both sides of the equality operator.

There is a similar difference when checking if the bits in a combined bit mask
have been cleared. When setting, clearing, or toggling bits, however, there

The only time a bitwise programmer has to think about the difference
between an individual bit mask and a combined bit mask is when
querying. There is no difference when setting, clearing, or toggling.

is no such difference. This is a critical point to grasp when working with bits. It
helps to explain why dynamically combined bit masks are very common when
setting or clearing bits. Here is an example from the core API:

availinfo &= ~(ImageObserver.SOMEBITS


| ImageObserver.FRAMEBITS

EXPRESSIONS, STATEMENTS, AND BLOCKS 485


| ImageObserver.ALLBITS
| ImageObserver.ERROR);

All of these bits are cleared, no different than if a statically combined bit mask
were used. The same could be said of toggling bits, but bits are seldom toggled.
Be careful when clearing one or more bits because, unlike the other opera-
tions, the bitwise negation operator is used to clear the bit (or bits) in the mask.
All other uses of the bitwise negation operator have the meaning of “bit(s) not in
the mask.” For example,

class Test {
public static void main(String[] args) {
int bitset = ~0;
System.out.println(BitPattern.toBinaryString(bitset));
final int MASK = 0x1;
bitset &= ~MASK; //clear this bit
System.out.println(BitPattern.toBinaryString(bitset));
bitset = ~0;
bitset &= MASK; //clear all but this bit
System.out.println(BitPattern.toBinaryString(bitset));
}
}

Executing this program prints

11111111 11111111 11111111 11111111


11111111 11111111 11111111 11111110
00000000 00000000 00000000 00000001

In short, clearing bits involves a somewhat counterintuitive use of the bitwise


negation operator.
I hesitate to discuss checking the value of a bit mask because the typesafe
enum pattern should be used to pass public bit masks. There are exam-
ples of how to use typesafe enums when bit processing in 1.5.5 Enumerated
Types. Having said that, coding argument checks for bit masks is problematic.
Interestingly, they share a common problem with typesafe enums. Using the
typesafe enum pattern, an argument check is still required to check for null.
Likewise, an argument check for a public bit mask must check for zero. The

486 JAVA RULES


real problem, however, is someone passing a combined bit mask when an indi-
vidual bit mask is expected. For example,

class Test {
public static final int TRUE = 1;
public static final int FALSE = 2;
public static void main(String[] args) {
test(0); //illegal argument
test(TRUE);
test(FALSE);
test(TRUE|FALSE); //illegal argument
}
public static void test(int mask) {
if ((mask & ~(TRUE|FALSE)) != 0)
System.out.println(
"throwing IllegalArgumentException " + mask);

switch(mask) {
case(TRUE): System.out.println("true");
break;
case(FALSE): System.out.println("false");
break;
default: System.out.println("assertion failure");
break;
}
}
}

Presumably assert false would be coded in the default label of the


switch statement. Executing this program prints

assertion failure
true
false
assertion failure

The argument check has two bugs. The first thing that needs to be done is to
add a check for zero:

if (mask == 0 || (mask & ~(TRUE|FALSE)) != 0)


System.out.println("throwing IllegalArgumentException" +
mask);

EXPRESSIONS, STATEMENTS, AND BLOCKS 487


This argument check still has a bug, however, because the method does not
expect a combined bit mask. Here is one solution to the problem:

if (mask == 0 || !(mask == TRUE || mask == FALSE))


System.out.println("throwing IllegalArgumentException" + mask);

This gets to be very awkward however when there are more bit masks with
longer names than TRUE and FALSE. Many programmers use a switch state-
ment under these circumstances. Such argument checks are still valid as com-
plex assertions in non- public methods. For example,

class Test {
private static final int TRUE = 1;
private static final int FALSE = 2;
public static void main(String[] args) {
test(0); //illegal argument
test(TRUE);
test(FALSE);
test(TRUE|FALSE); //illegal argument
}
private static void test(int mask) {
assert checkMask(mask);

switch(mask) {
case(TRUE): System.out.println("true");
break;
case(FALSE): System.out.println("false");
break;
default: System.out.println("assertion failure");
break;
}
}
private static boolean checkMask(int mask) {
switch(mask) {
case(TRUE):
case(FALSE):
return true;
default:
return false;
}
}
}

488 JAVA RULES


The switch statement is much simpler to read and probably more efficient. A
separate method is used so as not to clutter the test(int mask) method.
This is not really a performance problem because assertions are disabled by
default. Moreover, if assertions were enabled an optimized JVM would inline a
small private method such as this. I reiterate, however, that a typesafe enum
should be used to pass bit mask values in non-private methods.
If you need to provide access to the value of a bit without making it
public, boolean type accessor methods are frequently used. Here are
some typical examples from the Font class:

public boolean isBold() {


return (style & BOLD) != 0;
}

public boolean isItalic() {


return (style & ITALIC) != 0
}

Such boolean methods are important because they make it possible to encap-
sulate the bit set variable as well as the bit masks (assuming that a typesafe
enum is used to pass the values).
Everything you have seen up until now is what I would describe as standard
bit processing. It is extremely efficient, both in terms of CPU cycles and memory
usage. The remainder of this section discusses alternatives to standard bit pro-
cessing. None of these alternatives are quite as efficient as traditional bit pro-

In a world of bitwise programmers willing to sacrifice everything at


the alter of performance, BitSet is a false god.

cessing. In fact, some of them are considerably slower. I suspect hard-core


bitwise programmers look with cold disdain upon solutions such as the BitSet
class. For example, I found the following comment in java.text.Merge-
Collation.

// Using BitSet would make this easier, but it's significantly slower.

EXPRESSIONS, STATEMENTS, AND BLOCKS 489


Such solutions make it easier to use bits, but substantially negate their perfor-
mance advantage. For example, instead of executing the incredibly fast
bitset |= MASK to set a bit, here is the code required to implement the
set(int bitIndex) method in the BitSet class:

public void set(int bitIndex) {


if (bitIndex < 0)
throw new IndexOutOfBoundsException
(Integer.toString(bitIndex));
int unitIndex = unitIndex(bitIndex);
int unitsRequired = unitIndex+1;

if (unitsInUse < unitsRequired) {


ensureCapacity(unitsRequired);
bits[unitIndex] |= bit(bitIndex);
unitsInUse = unitsRequired;
} else {
bits[unitIndex] |= bit(bitIndex);
}
}

There really is no comparison. I searched the entire core API as well as all of the
support classes for the most recent J2SE implementation and could find only
eleven classes that use the BitSet class, whereas there are hundreds of
classes the use traditional bit processing. This is true even though the BitSet
class has been part of the core API since the 1.0 release.
The BigInteger class also has bit operations, but is an immutable class
that creates a new object every time a bit is set, cleared, or toggled. There is a
method for each of the basic bit operations:

public boolean testBit(int n)


public BigInteger setBit(int n)
public BigInteger clearBit(int n)
public BigInteger flipBit(int n)

Using the testBit() method as a test case, I can find only two uses of these
bit operations in either the core API or any of the support classes for the most
recent Windows implementation of the J2SE. One is in BigDecimal (which

490 JAVA RULES


doesn’t really count because BigInteger is an important part of the
BigDecimal implementation).
There is another alternative that makes bit processing easier without seri-
ously impacting performance. I first noticed this solution in the Jcomponent
class of the javax.swing package.34 What makes this solution so elegant is
that all of the left shifting is neatly tucked away in the following getBit(int
bit) and setBit(int bit, boolean value) methods.

private boolean getBit(int bit) {


int mask = (1 << bit);
return ((bitset & mask) != 0);
}

private void setBit(int bit, boolean value) {


if(value) {
bitset |= (1 << bit);
} else {
bitset &= ~(1 << bit);
}
}

I have changed what used to be == mask in the original method to != 0


because the major drawback using this style of bit processing is that combined
bit masks cannot be passed to these methods. Thus you must set and clear
bits one at a time. Thus this solution is very error prone. Note that the
setBit(int bit, boolean value) method both sets and clears a bit.
The great advantage to this solution is that it makes it possible to initialize bit
masks with integer literals. For example,

/** Private flags **/


private static final int REQUEST_FOCUS_DISABLED = 0;
private static final int IS_DOUBLE_BUFFERED = 1;
private static final int ANCESTOR_USING_BUFFER = 2;
private static final int IS_PAINTING_TILE = 3;
private static final int HAS_FOCUS = 4;
private static final int IS_OPAQUE = 5;
private static final int KEY_EVENTS_ENABLED = 6;

34. For the sake of consistent usage in this section, I had to rename the methods as well as their
parameters.

EXPRESSIONS, STATEMENTS, AND BLOCKS 491


private static final int FOCUS_INPUTMAP_CREATED = 7;
private static final int ANCESTOR_INPUTMAP_CREATED = 8;
private static final int WIF_INPUTMAP_CREATED = 9;
private static final int ACTIONMAP_CREATED = 10;
private static final int CREATED_DOUBLE_BUFFER = 11;
private static final int IS_PRINTING = 12;
private static final int IS_PRINTING_ALL = 13;

Basically instead of being actual bit masks, these constants are the shift dis-
tances required to compute the value of a bit mask. Thus the computed value of
the REQUEST_FOCUS_DISABLED bit mask is one (a power of two), not zero. As
stated above, zero is never used as a bit mask.
Code that uses this approach to bit processing is only slightly less efficient
than traditional bit processing because of additional method invocations and the
continual recomputation of mask values. The main advantage is readability. This
style of bit processing is much more readable than traditional bit processing, but
the performance degradation (however negligible) and the limitation of setting or
clearing one bit at a time will prevent it from ever becoming a programmer con-
vention. It simply does not pass the “best practice” muster.

4.7.2 Converting Nybbles to Hexadecimal Digits


In between a bit and a byte is a nybble. A nybble is four bits that can be con-
verted into a hexadecimal digit (0-9 and a-f or A-F). Unlike byte, the spelling
of nybble as opposed to nibble is not universal. Although the etymology of byte
is in dispute, nybble (or nibble) is an obvious reference to bite, which many
believe to be the original spelling of byte. (Others maintain that BYTE was origi-
nally an acronym.) If the spelling of “bite” was indeed changed to “byte” so as
not to confuse it with bit, that would explain changing “nibble” to “nybble.”
The programming technique discussed in this section is implemented in the
core API as the Integer.toHexString(int i) and Long.toHex-
String(long i) utility methods. These methods also convert the sign bit,
which effectively adds 232 or 264 to the value of a negative argument. Thus the
return value is the numerical equivalent of the two’s complement format. For
example, -1 converts to ffffffff. The following toHexString(int i)

492 JAVA RULES


method uses essentially the same implementation as the corresponding core
API methods:

static char[] hexDigits = {'0','1','2','3','4',


'5','6','7','8','9',
'a','b','c','d','e','f'};

static String toHexString(int i) {


char[] buf = new char[8];
int index = 8;
do {
buf[--index] = hexDigits[i & 0xF];
i >>>= 4;
} while (i != 0);
return new String(buf, index, 8 - index);
}

A do loop is used so that zero returns "0". Because the nybbles are processed
from right to left, the char[] buffer is loaded using a prefix decrement opera-
tor. Note that the hexDigits array can be easily changed to use upper case
A-F.
There are number of optimizations common to most hexadecimal conver-
sion methods:
• The do loop does not invoke Character.forDigit((i & 0xF),
16) for reasons explained below
• A char[] buffer is used in conjunction with the String(char[]
value, int offset, int count) constructor. The alternative of
using a StringBuffer is too slow
• The hexDigits array is static. Using a local array would be disastrous
because every conversion operation incurs the cost of allocating and load-
ing the array.
The Character.forDigit(int digit, int radix) method is a clas-
sic example of a method that cost more than it is worth. Here is one possible
implementation of that method:

final static char[] chars = {


'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,

EXPRESSIONS, STATEMENTS, AND BLOCKS 493


'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};

public static char forDigit(int digit, int radix) {


if ((digit >= radix) || (digit < 0)) {
return '\0';
}
if ((radix < MIN_RADIX) || (radix > MAX_RADIX)) {
return '\0';
}
return chars[digit];
}

Besides the overhead of method invocation, there are two argument checks. All
of this to evaluate a single array access expression.
The toHexString methods are very easy to understand which makes
them a good introduction to how the right-shift operators are used as conveyor
belts. 4.5.6 Bitwise Operators &, |, ^, >>, >>>, and << included the following.
In practice, the signed and unsigned right shift operators are most
often used interchangeably as conveyor belts at the end of which bits
are processed in groups of four or eight. Sign extension versus zero
filling is a moot point because the “conveyor belt” effectively stops
after the last bit is processed (bit 31 or 63). Thus the use of the signed
or unsigned right shift operator becomes a matter of style. There are
times when the use of the unsigned right-shift operator is absolutely
necessary, but that is not usually the case.

The are eight nybbles in an int. The following program illustrates the concept
of a conveyor belt in action.

class Test {
static char[] hexDigits = {'0','1','2','3','4',
'5','6','7','8','9',
'a','b','c','d','e','f'};

public static void main(String[] args) {


toHexString(1234567890); // 0x499602d2
}
static void toHexString(int i) {

494 JAVA RULES


do {
String s = BitPattern.toBinaryString(i);
System.out.println(s);
System.out.println(s.substring(31, 35) + " = 0x" +
hexDigits[i & 0xF]);
i >>>= 4;
System.out.println();
} while (i != 0);
}
}

Executing this program prints:

01001001 10010110 00000010 11010010


0010 = 0x2

00000100 10011001 01100000 00101101


1101 = 0xd

00000000 01001001 10010110 00000010


0010 = 0x2

00000000 00000100 10011001 01100000


0000 = 0x0

00000000 00000000 01001001 10010110


0110 = 0x6

00000000 00000000 00000100 10011001


1001 = 0x9

00000000 00000000 00000000 01001001


1001 = 0x9

00000000 00000000 00000000 00000100


0100 = 0x4

The bold shows what is left of the original int. Each iteration masks the four
low-order bits and converts them into a hexadecimal digit. Can you see how this
“conveyor belt” works? It is the same whether you are processing nybbles or
unsigned bytes. The loop continues until i is equal to zero. This is therefore an
example of when the unsigned right-shift operator is required.

EXPRESSIONS, STATEMENTS, AND BLOCKS 495


There is an important alternative to this programming technique. Rather than
using a hexDigits array, the numeric value of a nybble can be used as an off-
set from either '0' , 'a' or 'A'. This alternative programming technique takes
advantage of the fact that character codes in character sets such as ASCII in
Figure 4.4 are sequentially numbered (in this case from 0-127). Within code

Figure 4.4 ASCII code chart

charts such as this 0123456789, ABCDEF, and abcdef are like sub-arrays
of character codes that can be indexed by the value of the nybble. In the case of
ABCDEF or abcdef, ten must be subtracted from the value of the nybble in
order to use it as an index. (Remember, the value of a nybble is 0-15.) To con-
vert this index value to an actual character it must be added to the first charac-
ter code (the value of '0', 'a', or 'A') in the respective sub-array. For
example,

do {
int offset = i & 0xF; //value of the nybble
char digit = (char)((offset < 10) ?
('0' + offset) : ('a' + offset - 10));
} while ((i >>>= 4) > 0);

Most hexadecimal conversions use lowercase a-f. If 'a' is changed to 'A' in


this example, however, capital letters are used without having to invoke
Character.toUpperCase(char ch). This is a seductively clever imple-
mentation. It may appear as if using the numeric value of a nybble as an offset is

496 JAVA RULES


more efficient, but that is a fooler. Actual microbenchmark tests show the array
access expression is marginally faster.
There are times when converting the sign bit (i.e., adding 232 or 264 to nega-
tive numbers) makes the core API implementations useless. The following imple-
mentation uses a negative sign instead.

static String toHexString(int i) {


char[] string = new char[9];
int index = 9;
if (i == Integer.MIN_VALUE) { //otherwise i = -i is a bug
return "-2147483648";
}
boolean negative = i < 0;
if (negative) {
i = -i;
}
do {
string[--index] = hexDigits[i & 0xF];
i >>>= 4;
} while (i != 0);
if (negative) {
string[--index] = '-';
}
return new String(string, index , (9 - index));
}

If passed -255, this method returns -FF rather than ffffff01.

NOTE 4.1
The following section discusses a programming technique that does
not actually use the bitwise operators. Nevertheless, it cannot be un-
derstood unless thinking at the bit level, and is a natural to follow the
previous section on nybble to hexadecimal digit conversions.

EXPRESSIONS, STATEMENTS, AND BLOCKS 497


4.7.3 General-Purpose Integer to String Conversion Methods
There are essentially two programming techniques for converting integers to
strings. One only works in number systems such as binary, octal, and hexadeci-
mal in which a digit corresponds to an exact number of bits. That programming
technique can therefore use the right shift operators as a “conveyor belt” of
sorts and then mask the low-order bits. The previous section includes several
examples. The other programming technique is discussed in this section. It
must be used for the common case of converting integers to decimal digits
because decimal digits do not correspond to an exact number of bits. It
must also be used in general-purpose integer to string conversion meth-
ods, which is any integer to string conversion method that is passed a radix. The
reason again is that the radix may not be a power of two. Therefore the right
shift operators cannot be used. A different kind of “conveyor belt” is needed.
Likewise, masking the low-order bits does not work. This is the problem of
incommensurable bases as manifested in integer to string conversions.
The different kind of a “conveyor belt” discussed in this section is division by
the radix. For example,

class Test {
public static void main(String[] args) {
int i = 123456789;
System.out.println(i);
while (i != 0)
System.out.println(i /= 10);
}
}

Executing this program prints:

123456789
12345678
1234567
123456
12345
1234
123
12
1
0

498 JAVA RULES


This works for all number systems (including binary, octal, and hexadecimal)
because in positional notation systems place values are always multiples of the
radix. Dividing by the radix effectively truncates the value of the right-most digit
for a given number system. I refer to this use of / radix as place value shift-
ing though neither of the right shift operators is actually used.
The other difference is that “place values” are moving down the conceptual
conveyor belt instead of nybbles. If dividing by ten effectively truncates the right-
most digit, then the remainder of such a division operation must be the value of
the right-most digit. For example,

class Test {
public static void main(String[] args) {
int radix = 10;
int integer = 123456789;
System.out.println(integer % radix);
}
}

Executing this program prints 9. I refer to this use of % radix as a general-pur-


pose digit mask. Here again, % radix works for all number systems.

When converting integers to decimal digits, the use of % radix is


to “place value shifting” what & 0xf is to “nybble shifting.”

Here then is a complete example of the coding technique for converting inte-
gers to decimal digits:

static char[] digits = {'0','1','2','3','4',


'5','6','7','8','9'};

static String toString(int i) {


char[] string = new char[11];
int index = 11;
if (i == Integer.MIN_VALUE) { //otherwise i = -i is a bug
return "-2147483648";
}
boolean negative = i < 0;
if (negative) {
i = -i;

EXPRESSIONS, STATEMENTS, AND BLOCKS 499


}
do {
string[--index] = digits[i%10];
i = i / 10;
} while (i != 0);
if (negative) {
string[--index] = '-';
}
return new String(string, index , (11 - index));
}

Except for the differences discussed above this looks very much like the last
example in the previous section.
As a final note on integer to string conversions, the programming technique
discussed in this section is indeed used in the core API in general-purpose inte-
ger to string conversions such as the Integer.toString(int i, int
radix) and Long.toString(long i, int radix) methods. As of the
1.4 release, however, it is no longer used in the Integer.toString(int
i) method, which apparently is a “hot spot” because it is getting a lot of atten-
tion. Prior to the 1.4 release that method essentially used this programming
technique only with some very clever loop unrolling. The 1.4 implementation is
considerably different. It “avoids division by 10”35 by using what the responsible
programmer refers to as invariant division by multiplication.36 The resulting
implementation is impressively complex. Interestingly enough, “invariant division
by multiplication” (based on what I have seen in Integer.toString(int
i) because I have not yet read the paper) is essentially an optimization that has
figured out how to use the shift operators and bit masks for radices that are not
a power of two. If you are a teacher, I would suggest comparing the source
code for this method in the 1.3 release to the source code in the latest release.
Doing so would be an excellent lesson in source code optimization.

35. End-of-line comments in the source code for the Integer.toString(int i) method.
36. T. Gralund and P. Montgomery, “Division by Invariant Integers using Multiplication,” ACM PLDI
1994

500 JAVA RULES


4.2
NOTE The following section discusses how the bitwise operators are used to
convert primitive data types to and from a sequence of unsigned bytes
(otherwise known as a byte buffer) when reading and writing binary files.
The same programming techniques are used by network programmers
to send and receive binary data across a network. They are also used to
process IP addresses and RGB colors (or pixels), both of which are
composed of four unsigned bytes stored in an int. IP addresses and
RGB colors are briefly discussed at the very bottom of this section.

4.7.4 Unsigned Bytes


The byte data type is signed, as are all of the integral types in the Java pro-
gramming language except char. The sign bit means the range of the byte
data type is -128 to 127. This is not, however, what most people think of when
they hear the term byte. Unless referring specifically to the byte data type, the
term byte generally means an unsigned byte (sometimes referred to as an
octet37) with a range of 0 to 255 , which is a kind of universal data type under-
stood by almost every modern computer platform. The following program shows
the difference between the byte data type (a signed byte) and an unsigned
byte.

class Test {
public static void main(String[] args) {
byte b = (byte) 255;
System.out.println("signed byte = " + b);
System.out.println("unsigned byte = " + (b & 0xff));
}
}

The (byte) cast operator truncates the high-order bits in the int type
numeric literal so that b is initialized with the bit pattern 11111111. Executing
this program prints

37. Historically a byte has not always been eight bits; the term octet is used as an unambiguous ref-
erence to an eight-bit byte. It is particularly favoured by network programmers.

EXPRESSIONS, STATEMENTS, AND BLOCKS 501


signed byte = -1
unsigned byte = 255

It is just a matter of how the bit pattern is interpreted. By using the AND operator
with a 0xff mask, the signed byte data type can be used as if it were an
unsigned byte.
There are two programming techniques discussed in this section. One uses
the right-shift operators along with an 0xff byte mask to process the bytes in
primitive data types as if they were coming down a conveyor belt at the end of
which they are written to an output stream one (unsigned) byte at a time. For
example,

public final void writeInt(int v) throws IOException {


out.write((v >>> 24) & 0xff);
out.write((v >>> 16) & 0xff);
out.write((v >>> 8) & 0xff);
out.write((v >>> 0) & 0xff);
}

This method writes an int to a file as a sequence of four unsigned bytes.


Implicit in the order of the shift distances from greater to lesser is that this file is
in big-endian byte order. While the capability exists in both the java.io and
java.nio packages to write binary data in little-endian (or what is sometimes
referred to as Intel) byte order, the Java platform uses big-endian (or network)
byte order by default, which means the most significant byte is written first. Byte
order is an important part of any discussion of unsigned bytes.
The other programming technique discussed in this section uses the left-shift
operator in conjunction with either the addition operator or the bitwise OR opera-
tor to convert the same sequence of one or more unsigned bytes to a primitive
data type. For example, the following readInt() method is used to read the
same int written by the previous writeInt(int v) method.

public final int readInt() throws IOException {


int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)

502 JAVA RULES


throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}

Note that right-shifting loosely equates to writing, whereas left-shifting is used


when reading binary data. These examples are simplified versions of the
writeInt(int v) and readInt() methods in the DataOutputStream
and DataInputStream classes, respectively.38 These are primarily conver-
sion methods, not I/O operations. The writeInt(int v) method converts
from an int to a sequence of four unsigned bytes. The readInt() method
converts a sequence of four unsigned bytes to an int. Converting to and from
a sequence of unsigned bytes is easily the single most important use of the shift
operators in the core API. Before discussing these programming techniques in
detail, however, I want to explore the meaning of “a sequence of unsigned bytes”
as stored in a byte[] (or byte buffer).
All primitive data types in the Java programming language are big-endian.
Thus in order to write a primitive data type to a little-endian byte stream, the
bytes must be written one at a time in order to reverse their byte order. The
question that inevitably confronts a programmer trying to master the program-
ming techniques discussed in this section is: Why convert primitive data types to
a sequence of unsigned bytes that are written to a file in big-endian (or network)
byte order? Look closely at the writeInt(int v) method again. The bit
pattern of the primitive data type is unchanged as a result of this so-called
conversion. Why write an int one byte at a time if the byte order is the same?
When I first began to study the programming techniques discussed in this sec-
tion I intuitively thought there must be some way to move the primitive data type
“as is” to the output file. In other words, there must be some way to read or
write binary data without executing code such as that found in the
writeInt(int v) and readInt() methods above. Surprisingly, there is

38. All of the (simplified) source code examples are from the older java.io package because
java.nio uses a preprocessor, making the source code much more difficult to work with. In fact,
it took me about two hours just to locate the comparable methods in the java.nio package.
They are buried in the package-private java.nio.Bits utility class. As it turn out, the best place
to see both programming techniques in a single class is the package-private java.io.Bits util-
ity class. (Note the this says io not nio .)

EXPRESSIONS, STATEMENTS, AND BLOCKS 503


not (at least not using the java.io or java.nio packages in the core API).
All binary data must be converted to a sequence of unsigned bytes before
being written to a file or network socket. I count this as one of the things truly
hard to understand.
In this context, however, byte[] is not thought of as a data type, but
rather as a very special kind of data, not unlike the integers, decimal numbers,
characters, and Boolean values stored in one of the primitive data types are dif-
ferent kinds of data. How do we describe this kind of data? As used in the
java.io and java.nio packages, a byte[] or ByteBuffer is merely
the type of an arbitrary-length sequence of unsigned bytes (otherwise
known as binary data). Binary data, unlike the primitive data types, is not fixed
length. Therefore the only way for a Java program to pass binary data to the I/O
system of the host computer is as a byte[]. It is a question of being able to
address an arbitrary-length sequence of unsigned bytes on the heap
using only the data types available in the Java programming language.

The byte[] data type is the only data type in the Java programming
language that can address an arbitrary number of unsigned bytes.

This explains why the central abstraction in the older java.io package is
referred to as a byte stream. In the newer java.nio package, the term byte
channel is used instead. In either case, it is a byte something. Personally, I pre-
fer byte train. Then you can think of unsigned bytes as open boxcars on a rail-
road track standing ready to transport eight bits of binary data either to a file or
across a network to another computer. Stops along the track are referred to as
byte (or file) buffers. (One of the problems with the old java.io package is
that all of the trains are local.) If you can grasp the special significance of the
byte[] data type as used in the java.io and java.nio packages, you will
have a much better understanding of why byte buffers, byte streams, byte chan-
nels, etc. are so called.
Now we are ready to discuss how the bitwise operators are used to convert
primitive data types to a sequence of unsigned bytes, and vice versa. First a few

504 JAVA RULES


comments on matters of style. Here again is the writeInt(int v) method
from above:

public final void writeInt(int v) throws IOException {


out.write((v >>> 24) & 0xff);
out.write((v >>> 16) & 0xff);
out.write((v >>> 8) & 0xff);
out.write((v >>> 0) & 0xff);
}

Parentheses are not required around the shift operations because shift opera-
tors have a higher level of precedence than bitwise operators. They are often
used, however, as a matter of style. Also, right shifting with a shift distance of
zero accomplishes nothing. It is done merely to make the code more readable.
The writeInt(int v) method could have been written as follows.

public final void writeInt(int v) throws IOException {


out.write(v >>> 24 & 0xff);
out.write(v >>> 16 & 0xff);
out.write(v >>> 8 & 0xff);
out.write(v & 0xff);
}

When converting primitive data types to a sequence of unsigned bytes using this
style of coding, the use of the right-shift operator versus the unsigned right-shift
operator is also a matter of style. This discussion is momentarily differed.
Now for the readInt() method. The source code is repeated here for
your convenience:

public final int readInt() throws IOException {


int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}

The if statement is checking to see if any of the four in.read() method


invocations returned -1 as an end-of-file (EOF) marker (or more precisely if the

EXPRESSIONS, STATEMENTS, AND BLOCKS 505


high-order bit is set in any of the unsigned bytes read). This code shows why
using EOFException as if it were an EOF marker is dangerous. The exception
is intended to signify that there are not enough unsigned bytes left in the input
stream to convert to the primitive data type, in this case a four byte int. That
would be indicative of a corrupted file.
Methods comparable to readInt() but that read from buffers and there-
fore do not have to check for EOF can be written more concisely as a single
return statement. The following example is from the ICC_Profile class in
the java.awt.color package.

static int intFromBigEndian(byte[] array, int index) {


return (((array[index] & 0xff) << 24) |
((array[index+1] & 0xff) << 16) |
((array[index+2] & 0xff) << 8) |
(array[index+3] & 0xff));
}

If you are confused as to why this example uses & 0xff and the readInt()
methods does not, the difference is data types. This example is accessing a
byte[] . Reading from an input stream in the java.io package returns an
int . I will explain the significance of this difference momentarily.
There is only one matter of style to discuss when converting from sequence
of unsigned bytes to a primitive data type (or reading binary data). There are
four different operators that can be used to “add” or “reassemble” the unsigned
bytes after they have been shifted back to their original positions: +, |, +=, and
|= . The only appreciable difference from a performance perspective is
that the compound operators are slower. For example,
import java.util.*;
class Test {
static int retval=0;
static int[] unsignedBytes = {'t','e','s','t'};
public static void main(String[] args) {
test1(10000000, true);
test2(10000000, true);
test3(10000000, true);
test4(10000000, true);
System.out.println();

506 JAVA RULES


test1(100000000, false);
test2(100000000, false);
test3(100000000, false);
test4(100000000, false);
System.out.println();
test1(1000000000, false);
test2(1000000000, false);
test3(1000000000, false);
test4(1000000000, false);
System.out.println();
test1(2000000000, false);
test2(2000000000, false);
test3(2000000000, false);
test4(2000000000, false);
}
static void test1(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
int ch1 = unsignedBytes[0];
int ch2 = unsignedBytes[1];
int ch3 = unsignedBytes[2];
int ch4 = unsignedBytes[3];
retval = (ch1 << 24) + (ch2 << 16) +
(ch3 << 8) + (ch4 << 0);
}
timer.stop();
if (!JVMwarmup) {
//System.out.println("retval = " + retval);
System.out.println(count +
" using the + operator " +
timer.getElapsedTime());
}
}
static void test2(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
int ch1 = unsignedBytes[0];
int ch2 = unsignedBytes[1];
int ch3 = unsignedBytes[2];
int ch4 = unsignedBytes[3];
retval = ch1 << 24 | ch2 << 16 | ch3 << 8 | ch4 << 0;
}
timer.stop();
if (!JVMwarmup) {

EXPRESSIONS, STATEMENTS, AND BLOCKS 507


//System.out.println("retval = " + retval);
System.out.println(count +
" using the | operator " +
timer.getElapsedTime());
}
}
static void test3(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
retval = unsignedBytes[0] << 24;
retval += unsignedBytes[1] << 16;
retval += unsignedBytes[2] << 8;
retval += unsignedBytes[3];
}
timer.stop();
if (!JVMwarmup) {
//System.out.println("retval = " + retval);
System.out.println(count +
" using the += operator " +
timer.getElapsedTime());
}
}
static void test4(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
retval = unsignedBytes[0] << 24;
retval |= unsignedBytes[1] << 16;
retval |= unsignedBytes[2] << 8;
retval |= unsignedBytes[3];
}
timer.stop();
if (!JVMwarmup) {
//System.out.println("retval = " + retval);
System.out.println(count +
" using the |= operator " +
timer.getElapsedTime());
}
}
}

Here is some typical output on my computer:

100000000 using the + operator 735


100000000 using the | operator 781

508 JAVA RULES


100000000 using the += operator 1516
100000000 using the |= operator 1578

1000000000 using the + operator 7375


1000000000 using the | operator 7640
1000000000 using the += operator 15110
1000000000 using the |= operator 15734

2000000000 using the + operator 14578


2000000000 using the | operator 15313
2000000000 using the += operator 30265
2000000000 using the |= operator 31469

These tests accurately reflect coding styles found in the core API. I would cau-
tion against making too much out of the difference between the + and | opera-
tors (or between the += and |= operators for that matter). With so many
iterations (two billion in the last test), those differences are best described as
miniscule. I say this because I strongly prefer the | (inclusive OR) operator
as a matter of style because it does not require parentheses around the
operands and simply looks better.
In fact, I think the bitwise operators should be used exclusively or not at
all. That excludes any use of compound assignment operators in bitwise pro-
gramming. The majority of the core API programmers apparently agree because
the | (inclusive OR) operator is definitely the most commonly used operator in
methods such as readInt(). Use of the compound assignment operators
when converting a sequence of unsigned bytes to a primitive data types is far
less common. Here is a very clean implementation the readInt() method
using only bitwise operators and eliminating the unnecessary parentheses:

public final int readInt() throws IOException {


int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ch1 << 24 | ch2 << 16 | ch3 << 8 | ch4 << 0;
}

EXPRESSIONS, STATEMENTS, AND BLOCKS 509


Now consider this example from java.net.URLConnection that uses
compound addition operators:

if(byteOrder == 0xFE) {
sectDirStart = is.read();
sectDirStart += is.read()<<8;
sectDirStart += is.read()<<16;
sectDirStart += is.read()<<24;
}
else {
sectDirStart = is.read()<<24;
sectDirStart += is.read()<<16;
sectDirStart += is.read()<<8;
sectDirStart += is.read();
}

Here is what this example would look like using the | (inclusive OR) operator:

if(byteOrder == 0xFE) {
sectDirStart = is.read() |
is.read() << 8 |
is.read() << 16 |
is.read() << 24;
} else {
sectDirStart = is.read() << 24 |
is.read() << 16 |
is.read() << 8 |
is.read();
}

This is just a much cleaner and easier to read implementation.


While it is customary to add unsigned bytes starting with the most significant
byte, it should be understood that this is merely a programmer convention if the
unsigned bytes are stored in variables (versus being read from an input stream).
Here is a rare example of flouting this programmer convention from a class in
the sun.awt package.

byte[] rowByteBuffer = new byte[rowByteWidth];



wPixels[col+rowOffset] =
((rowByteBuffer[spos ]&0xff)<<16)
| ((rowByteBuffer[spos+1]&0xff)<< 8)

510 JAVA RULES


| ((rowByteBuffer[spos+2]&0xff) )
| ((rowByteBuffer[spos+3]&0xff)<<24);

If reading from an input stream the order of shift distances must reflect the big
or little-endian byte order of the input data.
The most important matter of style is one that I would argue should not be a
matter of style. Most of the examples in this section conform to the following
programmer conventions:
• When converting primitive data types to a sequence of unsigned bytes (or
writing binary data), the programmer convention is to (right) shift and then
mask
• When converting a sequence of unsigned bytes to a primitive data type (or
reading binary data), the programmer convention is to mask and then (left)
shift. However, masking is only necessary when the unsigned bytes are
stored in a byte or byte[]
The main reason why these are (or rather should be) programmer conventions
and not matters of style is that the alternatives require more complicated bit
masks. For example,

public final void writeInt(int v) throws IOException {


out.write((v & 0xFF000000) >>> 24);
out.write((v & 0xFF0000) >> 16);
out.write((v & 0xFF00) >> 8);
out.write(v & 0xFF);
}

Note also that the unsigned right-shift operator is required when shifting the
most significant byte. It’s use when masking and then shifting is definitely not a
matter of style. I would argue, too, that right shifting and then masking (the con-
veyor belt), is more intuitive than this alternative.
Here is a comparable example of converting a sequence of unsigned bytes
to a primitive data type from a class in the com.sun package:

private static int bytesToInt(byte[] array, int offset) {


int b1, b2, b3, b4;

b1 = (array[offset++] << 24) & 0xFF000000;


b2 = (array[offset++] << 16) & 0x00FF0000;

EXPRESSIONS, STATEMENTS, AND BLOCKS 511


b3 = (array[offset++] << 8) & 0x0000FF00;
b4 = (array[offset++] << 0) & 0x000000FF;

return (b1 | b2 | b3 | b4);


}

Here again the more complicated bit masks are the primary problem. However,
this too suffers from the problem of being less intuitive.
Now I can discuss the use of the right-shift operator versus the unsigned right-
shift operator when converting primitive data types to sequences of unsigned
bytes. The following is from 4.5.6 Bitwise Operators &, |, ^, >>, >>>, and <<.
In practice, the signed and unsigned right shift operators are most
often used interchangeably as conveyor belts at the end of which bits
are processed in groups of four or eight. Sign extension versus zero
filling is a moot point because the “conveyor belt” effectively stops
after the last bit is processed (bit 31 or 63). Thus the use of the signed
or unsigned right shift operator becomes a matter of style.

This is only true when using the programmer convention of (right) shift-
ing and then masking. To show how this works, let us look at the difference
between the right-shift and unsigned right-shift operators:

class Test {
public static void main(String[] args) {
int i = 0x80000000; //sets the sign bit to 1
System.out.println(BitPattern.toBinaryString(i));
System.out.println(BitPattern.toBinaryString(i >>> 24));
System.out.println(BitPattern.toBinaryString(i >> 24));
}
}

Executing this program prints

10000000 00000000 00000000 00000000


00000000 00000000 00000000 10000000
11111111 11111111 11111111 10000000

I refer to shifting in multiples of eight as byte shifting. (In the previous section
there are examples of right shifting with a shift value of four and using a 0xF
nybble mask. For consistency’s sake, this could be referred to a nybble shift-

512 JAVA RULES


ing.) Byte shifting is by far the most common use of the shift operators. A
shift value of 8 shifts a distance of one byte to the left or right; 16 shifts a dis-
tance of two bytes; 24 three bytes; and so on. When byte shifting the highest
shift distance is always eight less then the number of bits in a primitive data
type. In this example, the most significant byte is shifted to the right three bytes
(not bits) to the end of what I like to think of as a conveyor belt. All the other bits
are zero or sign extended, depending on which right-shift operator is used. The
reason it doesn’t matter which right-shift operator is used is simply that the zero
or sign extended bits to the left of the original sign bit are not used. For exam-
ple,

class Test {
public static void main(String[] args) {
int i = 0x80000000; //sets the sign bit to 1
System.out.println(BitPattern.toBinaryString(i));
System.out.println(BitPattern.toBinaryString
(i >>> 24 & 0xff));
System.out.println(BitPattern.toBinaryString
(i >> 24 & 0xff));
}
}

Executing this program prints

10000000 00000000 00000000 00000000


00000000 00000000 00000000 10000000
00000000 00000000 00000000 10000000

This is not the same output as before. The sign extended 1 bits in the second bit
pattern are now 0 because of the bitwise AND operator.
If you would like to see an example of when the unsigned right-shift operator
is absolutely necessary, take a look at this slightly modified version of a method
in com.sun.media.sound.SunFileReader:

int toLittleEndian(int i) {
int b1, b2, b3, b4 ;
b1 = (i & 0xFF) << 24 ;
b2 = (i & 0xFF00) << 8;
b3 = (i & 0xFF0000) >> 8;
b4 = (i & 0xFF000000) >>> 24;

EXPRESSIONS, STATEMENTS, AND BLOCKS 513


return ( b1 | b2 | b3 | b4 );
}

I have tried several alternatives for converting from big- to little-endian and think
this programmer has it right. The unsigned right-shift operator is absolutely nec-
essary because if i were negative, the signed right-shift operator would result in
a very large negative number instead of an unsigned byte in the range of 0 to
255. The do statement in the toHexString(int i) method in 4.7.2 Con-
verting Nybbles to Hexadecimal Digits is another example when using the
unsigned right-shift operator is required. Such examples, however, are rare. This
finishes the discussion on matters of style. I will now address more significant
issues such as inadvertent sign extension.
Inadvertent sign extension resulting from numeric promotion is a menacing
bug that constantly threatens bitwise programmers. Therefore it is important to
understand the following.

Inadvertent sign extension resulting from numeric promotion can


only happen when an unsigned byte is stored in either the byte or
byte[] data type. This explains why the java.io designers
choose to pass around unsigned bytes using the int data type.

If an unsigned byte is incorrectly promoted and the most significant bit happens
to be 1 the value of the unsigned byte changes as a result. For example,

class Test {
public static void main(String[] args) {
/*
* These are the values of the unsigned bytes
* in a int that is equal to 255
*/
byte b1 = 0;
byte b2 = 0;
byte b3 = 0;
byte b4 = -1; //all bits are set to 1

int i = b1 << 24 | b2 << 16 | b3 << 8 | b4; // WRONG


System.out.println(i);

514 JAVA RULES


System.out.println(BitPattern.toBinaryString(i));

i = (b1 & 0xff) << 24 | (b2 & 0xff)<< 16 | // RIGHT


(b3 & 0xff) << 8 | (b4 & 0xff);
System.out.println(i);
System.out.println(BitPattern.toBinaryString(i));
}
}

Executing this program prints

-1
11111111 11111111 11111111 11111111
255
00000000 00000000 00000000 11111111

It is imperative that whenever you are reading binary data and converting
sequences of unsigned bytes to primitive data types that you stop and consider
the data type of the unsigned bytes. If it is either byte or byte[], masking is
required to prevent inadvertent sign extension resulting from numeric promotion.
I should point out, however, that the most significant byte never really needs to
be masked. In the example above, the alternative of b1 << 24 would indeed
result in sign extension of the left-hand operand, but all the sign extended bits do
an exit stage left. Nevertheless, I have never seen a programmer actually omit
the & 0xff for the most significant byte when left shifting an unsigned byte
stored in either a byte or byte[].
There is an unwritten rule when converting a sequence of unsigned bytes not
stored in either a byte or byte[] (which typically means stored in an int) to
a primitive data type. A 0xff mask is not used on the value of the unsigned
byte prior to left shifting. This explains why readInt() and other conversion
methods that read from byte streams do not use & 0xff on the value of the
unsigned byte. (The result type of the read() method in the java.io pack-
age is int.) There are two reasons for this:
• Assuming that the value of the int or other non-byte integral data type is
actually an unsigned byte, masking is completely unnecessary because

EXPRESSIONS, STATEMENTS, AND BLOCKS 515


there is no possibility of inadvertent sign extension (even if a short or
char is used)
• If in fact the value is greater than 255, masking the unsigned byte would
also be masking a problem elsewhere in the system. You could assert that
the value was less than or equal to 255, but at the bit level such an asser-
tion would be absurdly inefficient even when disabled. When faced with a
potential problem such as this, it is always best to let the error pass through
your code. It will surface further downstream.
Characterizing this as an “unwritten rule” elevates it somewhere above a pro-
grammer convention. The fact is that examples of such unnecessary masking
are extremely rare.
There is only one other time when inadvertent sign extension is a problem.
Because bitwise operator expressions evaluate to either an int or long, the
implicit conversion in a return statement is never a problem when the result
type is either int or float. When it is long or double, however, the
expression in the return statement must be explicitly promoted. This is an
interesting problem when working with unsigned bytes because the shift opera-
tors do not use binary numeric promotion. Consequently a numeric suffix cannot
be used on the sift distance as a means of making sure the shift expression eval-
uates to a long. The correct solution to this problem is to use the numeric suf-
fix on the byte mask. For example,

return ((buf[pos + 0] & 0xffL) << 56) +


((buf[pos + 1] & 0xffL) << 48) +
((buf[pos + 2] & 0xffL) << 40) +
((buf[pos + 3] & 0xffL) << 32) +
((buf[pos + 4] & 0xffL) << 24) +
((buf[pos + 5] & 0xffL) << 16) +
((buf[pos + 6] & 0xffL) << 8) +
((buf[pos + 7] & 0xffL) << 0);

return Double.longBitsToDouble(
((buf[pos + 0] & 0xffL) << 56) +
((buf[pos + 1] & 0xffL) << 48) +
((buf[pos + 2] & 0xffL) << 40) +
((buf[pos + 3] & 0xffL) << 32) +
((buf[pos + 4] & 0xffL) << 24) +
((buf[pos + 5] & 0xffL) << 16) +

516 JAVA RULES


((buf[pos + 6] & 0xffL) << 8) +
((buf[pos + 7] & 0xffL) << 0));

These are the return statements from the readLong() and read-
Double() methods in ObjectInputStream. Note also that shift distances
greater than 31 can only be used in expressions that evaluate to long. Other-
wise, some or all of the shifted bits will do an exit stage left.
A (long) cast operator is sometimes used to promote the value being
shifted. Here is an example from the java.io package:

public final long readLong() throws IOException {


InputStream in = this.in;
return ((long)(readInt()) << 32) + (readInt() & 0xffffffffL);
}

Note that the parentheses around readInt() are unnecessary. This example
falls under the rule of using the bitwise operators exclusively or not at all. As a
matter of style, it should have been written as follows.

public final long readLong() throws IOException {


InputStream in = this.in;
return ((readInt() & 0xffffffffL) << 32) |
(readInt() & 0xffffffffL);
}

Here is some really questionable code from the core API:

public long getValue() {


return (long)crc & 0xffffffffL;
}

The cyclic redundancy code (or CRC) is stored in an int. What you have to keep
in mind when looking at this code is that the cast operator is a unary operator.
Only the value of crc is cast. At that point the crc is incorrectly sign extended,
the effect of which is negated by the 0xffffffffL mask. You should always
be on the lookout for this mistake of using both a cast operator and bit masking
on the same value. Although I have seen several examples of this being done in
the core API, it is always a mistake. If it is a narrowing primitive conversion that
requires a cast operator (typically storing the value of an int in a byte or
byte[]), then & 0xff masking is redundant.

EXPRESSIONS, STATEMENTS, AND BLOCKS 517


That more or less finishes the discussion of reading and writing binary data.
There is one last subject I want to discuss, however, before moving on to IP
addresses and RGB colors. That is the use of loops. The programming tech-
niques discussed in this section can be coded in a loop. Here is a rare example
from a com.sun class:

for (int i = 0; i < lengthbyte; i++) {


retval = (retval << 8) + (buf[offset++] & 0xff);
}

This for loop is from a method that is passed variable length data. Otherwise a
loop would not have been used. The reason why is a very common optimization
known as loop unrolling. Loops have significant overhead; every iteration has
to evaluate a boolean type expression to determine if the loop should continue
to execute. For example,

import java.util.*;
class Test {
static int retval=0;
static int[] unsignedBytes = {'t','e','s','t'};
public static void main(String[] args) {
test1(100000, true);
test2(100000, true);
test1(1000000, false);
test2(1000000, false);
test1(10000000, false);
test2(10000000, false);
test1(100000000, false);
test2(100000000, false);
test1(1000000000, false);
test2(1000000000, false);
}
static void test1(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
int ch1 = unsignedBytes[0];
int ch2 = unsignedBytes[1];
int ch3 = unsignedBytes[2];
int ch4 = unsignedBytes[3];
retval = ch1 << 24 | ch2 << 16 | ch3 << 8 | ch4 << 0;
}

518 JAVA RULES


timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" loop unrolling " +
timer.getElapsedTime());
}
}
static void test2(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
for (int j=0; j<4; j++)
retval = (retval << 8) | unsignedBytes[j];
}
timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using a for loop " +
timer.getElapsedTime());
}
}
}

This code is modeled after the core API example, and so uses retval as a
variable name. I have gone out of my way so as not to give loop unrolling an
unfair advantage:
• The numeric literal 4 is used as the loop control variable instead of
unsignedBytes.length
• The & 0xff mask has been removed because it is normal to assume that
an int used as an unsigned byte actually does have a value <= 255
• The | operator is used instead of +
Here is some typical output on my computer:

1000000 loop unrolling 16


1000000 using a for loop 31
10000000 loop unrolling 78
10000000 using a for loop 344
100000000 loop unrolling 781
100000000 using a for loop 3359
1000000000 loop unrolling 7782
1000000000 using a for loop 33750

EXPRESSIONS, STATEMENTS, AND BLOCKS 519


Loop unrolling in this case is four times faster. All of the read and write
methods in this section are examples of loop unrolling.
Now that you have mastered these two programming techniques, I will show
some examples of how they are used elsewhere in the core API. Two uses stand
out above all the rest. These are IP addresses and RGB colors. Both consist of
four unsigned bytes of data stored in an int. There are plenty of lessor exam-
ples of these programming techniques in the core API, however, such as the
toByteArray() method in the CollationKey class. The idea is merely to
develop an appreciation for how the bitwise operators are used by discussing
some of their more common uses in the core API. I will start with IP addresses.
An IP address (Internet Protocol address, or Internet address for short) is
represented by the InetAddress class in the java.net package. For the
one percent of the readers who need such an explanation, an IP address is to
your computer what your telephone number is to cell phone. It identifies a partic-
ular computer on the Internet. Each IP address consists of four unsigned bytes
with a range of 0 to 255, which identify a network and a “host” (the computer).
They are usually shown as four decimal numbers separated by periods. For
example, 168.212.226.204 is an IP address.
Internally, InetAddress stores the IP address in an int. The
getHostName() method converts that int into a sequence of unsigned
bytes using exactly the same programming technique used to write binary data
to a file or network socket:

int address;

public String getHostAddress() {


return ((address >>> 24) & 0xff) + "." +
((address >>> 16) & 0xff) + "." +
((address >>> 8) & 0xff) + "." +
((address >>> 0) & 0xff);
}

You will recall from the discussion above that the parentheses around the shift
operations are unnecessary, and using the unsigned right-shift operator is purely
a matter of style. The maximum string length is 15 so the default initial capacity
of a StringBuffer is adequate.

520 JAVA RULES


Raw IP addresses are passed around as a byte[] (always in network byte
order). For example, there is the package-private InetAddress(String
hostName, byte addr[]) constructor. Here is the code in that constructor
that converts a sequence of four unsigned bytes into the int named
address:

address = addr[3] & 0xff;


address |= ((addr[2] << 8) & 0xff00);
address |= ((addr[1] << 16) & 0xff0000);
address |= ((addr[0] << 24) & 0xff000000);

My hope is that because of the more complicated bit masks that you immedi-
ately recognized that the programmer convention for converting a sequence of
unsigned bytes stored in a byte or byte[] to a primitive data type is not
being used. As a matter as style, the bitwise operators should be used exclu-
sively, or not at all. I would have written this as follows.

address = (addr[0] & 0xff) << 24) | (addr[1] & 0xff) << 16) |
(addr[2] & 0xff) << 8) | addr[3] & 0xff;

Do you see how these conversion methods are all the same? Now consider RGB
colors.
Digital images are stored in an int[] in which the elements are referred to
as pixels (or picture elements). The size of a digital image is measured in pixels.
The X and Y coordinates for a particular pixel are not actually stored in the int.
It is a factor of how the image is displayed and the position of the pixel in the
array. The int is used to store only the color information. An int is sufficient
to display images in True Color, which is usually describe as “more than 16 mil-
lion colors.” True Color is simply 2553 or 16,581,375 colors using RGB colors
(Red, Green, Blue), which are a sequence of four unsigned bytes. The Java pro-
gramming language uses a standard RGB color referred to as the sRGB, which
is the default color space for the Java 2D package. For more information on
sRGB, see www.w3.org/pub/WWW/Graphics/Color/sRGB.html.
Why four unsigned bytes if there are only three colors? The first byte is
referred to as the Alpha value, and is explained as follows in the API docs for
the java.awt.Color class:

EXPRESSIONS, STATEMENTS, AND BLOCKS 521


Every color has an implicit alpha value of 1.0 or an explicit one pro-
vided in the constructor. The alpha value defines the transparency of a
color and can be represented by a float value in the range 0.0 - 1.0 or
0 - 255. An alpha value of 1.0 or 255 means that the color is com-
pletely opaque and an alpha value of 0 or 0.0 means that the color is
completely transparent.39

So for the purpose of this discussion, we can think of a pixel as a sequence of


four unsigned bytes stored in an int, not unlike an IP address.
Now let us see how the programming techniques discussed in this section
are used in the Color class. Here is a simplified constructor from that class:

public Color(int r, int g, int b, int a) {


value = ((a & 0xff) << 24) |
((r & 0xff) << 16) |
((g & 0xff) << 8) |
((b & 0xff) << 0);
}

Look familiar? This is exactly the same code used by the readInt() method
with one important exception. The & 0xff bit masking is not required if the int
type parameters are indeed passing unsigned bytes, so we should expect the
API docs to include some notice that the arguments passed are “reduced” mod-
ulo 256. Surprisingly, the API docs for this constructor says only the following.
Creates an sRGB color with the specified red, green, blue, and alpha
values in the range (0 - 255). 40

I would have made the warning more explicit, even though you can expect
graphic programmers to be fully aware of this issue. Here is some code from
the getDataElements(int rgb, Object pixel) method that converts
a pixel into individual RGB colors:

int red = (rgb>>16) & 0xff;


int green = (rgb>>8) & 0xff;

39. API docs for the java.awt.Color class.


40. API docs for the Color(int r, int g, int b, int a) constructor in the
java.awt.Color class.

522 JAVA RULES


int blue = rgb & 0xff;
int alpha = (rgb>>>24);

One of those irreverent programmers doing things out of order! Otherwise, per-
fectly written in terms of following the programmer convention and not doing
anything unnecessary. (The parentheses are unnecessary.)

4.7.5 Some Miscellaneous Uses of the Bitwise Operators


This section discusses some of the more obscure uses of the bitwise operators
in the core API. In the binary notation system, for example, bit 0 is always set if a
number is odd, which makes it very easy to determine if a number is odd or
even. For example,

class Test {
public static void main(String[] args) {
for (int i=0; i<= 5; i++) {
if ((i & 1) == 0)
System.out.println(i + " is even");
if ((i & 1) != 0)
System.out.println(i + " is odd");
}
}
}

Executing this program prints

0 is even
1 is odd
2 is even
3 is odd
4 is even
5 is odd

The bitwise AND operator should always be used to make this determination.
The following if statement can be used to test for powers of two.

if ((n & -n) == n) // i.e., n is a power of 2

For example,

class Test {
public static void main(String[] args) {

EXPRESSIONS, STATEMENTS, AND BLOCKS 523


for (int n=0; n<5000; n++)
if ((n & -n) == n)
System.out.println(n);
}
}

Executing this program prints

0
1
2
4
8
16
32
64
128
256
512
1024
2048
4096

This use of bitwise operators is very common in methods that rehash hash
tables. For example,

void rehash(int newCapacity) {


assert (newCapacity & -newCapacity) == newCapacity;

If you need to test for a power of two there is nothing more efficient.
The core API uses at least three different programming techniques for align-
ing text to a four byte boundary. The first uses the remainder operator:

class Test {
public static void main(String[] args) {
String string = "123456";
int align = 4 - (string.length() % 4);
if (align == 4)
align = 0;
length = length + align;
System.out.println(length);
}
}

524 JAVA RULES


Executing this program prints 8. Here is another solution to the same problem:

class Test {
public static void main(String[] args) {
String string = "123456";
int length = string.length();
int incr = length & 3;
if (incr != 0) {
incr = (4 - incr);
length += incr;
}
System.out.println(length);
}
}

Executing this program also prints 8. A more elegant solution, however, is


string.length()+3 & ~3, the value of which is always a multiple of four.
The same formula works for any power of two. Here is an actual example from
the core API:

int minwidth = labelWidth + 4;


minwidth = (minwidth + 15) & ~15;

This expression evaluates to some multiple of 16.


The sign bit of any primitive numeric data type can be masked using a hexa-
decimal literal that begins 0x80. Can you tell what the following code is doing?

if ((temp[0] & 0x80) != 0)


throw new IllegalArgumentException("negative BigInteger");

This is an argument check from the core API. It could just as well have been writ-
ten as follows.

if (temp[0] < 0)
throw new IllegalArgumentException("negative BigInteger");

This line of code is from the java.math package, however, and the responsi-
ble programmer is a master of bitwise programming.
All of the other bits except the sign bit can be masked using a hexadecimal
literal that begins 0x7f. For example, the sign bit for an int is masked using &
0x80000000 and all of the other bits are masked using & 0x7fffffff.

EXPRESSIONS, STATEMENTS, AND BLOCKS 525


Note that 0x7fffffff (and similar hexadecimal literals for the other integral
types) is used to initialize Integer.MAX_VALUE. When used with the inclu-
sive OR operator, such literals can be used to strip off the sign bit in an integral
type. You will see this most often in methods that compute the index of an object
stored in a hash table. For example,

int index = (hashCode & 0x7FFFFFFF) % table.length;

You should be familiar with this and similar lines of code because many classes
declare their own hash tables and include code such as this. See also 4.11.2
Understanding Hash Tables in Volume 1.
Now we begin to look at some of the more questionable uses of the bitwise
operators in the core API. Any variable that is incremented using the ++ opera-
tor will always have the low order bit set if the variable is not equal to zero. Thus
the low order bit can be masked as a means of testing if the variable has been
incriminated at all. Here is an example from the core API:

return ((hits & 1) != 0);

This method returns true if hits has been incriminated during execution of
the method. Here is a more elaborate example also from the core API:

int crossings = 0;

while (--roots >= 0) {
if (x < res[roots]) {
crossings++;
}
}
return ((crossings & 1) == 1);

In both cases the expression in the return statement could have been coded
hits > 0 or crossings > 0, which would have been more readable.

4.8 Statements
A Java program is executed one statement at a time. Every statement in Java is
one of the following:

526 JAVA RULES


• Control-flow statements (if, switch, for, while, and do)
• Control-transfer statements (return, throw, break, and continue)
• Expression statements
• Declaration statements (local variables and classes)
• synchronized statement
• try statement
• The empty statement
Note that iteration statements (or loops) are a subset of the control-flow state-
ments. Control-flow statements and the closely related break and continue
statements are discussed in the following subsections. Declaration statements
and the empty statement are discussed in the remainder of this section; expres-
sion statements which are discussed in 4.2.2 Expression Statements and Other
Top-Level Expressions; the synchronized statement is discussed in an as
yet unpublished volume of Java Rules; the throw and try statements are dis-
cussed in Chapter 6, “Exceptions and Logging;” and finally the return state-
ment is discussed in 1.6.2 Result Types and the return Statement.
A declaration statement is a declaration that is also an executable state-
ment (not unlike an expression statement). There are only two declaration state-
ments in the Java programming language—local variable declaration
statements and local class declaration statements. As would be expected,
the entity created by a declaration statement cannot be referenced until the dec-
laration statement is executed. For example:

class Test {
public static void main(String[] args) {
LocalClass localClass = new LocalClass();
class LocalClass { }
}
}

Attempting to compile this program generates the following compiler errors:

Test.java:3: cannot resolve symbol


symbol : class LocalClass
location: class Test
LocalClass localClass = new LocalClass();

EXPRESSIONS, STATEMENTS, AND BLOCKS 527


^
Test.java:3: cannot resolve symbol
symbol : class LocalClass
location: class Test
LocalClass localClass = new LocalClass();
^
2 errors

If the two lines of code in the main method are reversed, this program compiles
without generating any compiler errors. The same is true of local variable decla-
ration statements.
The empty statement is nothing more than a semicolon, and is similar to
the syntax used to indicate a missing method implementation in either
abstract or native method declarations. The empty statement can be
used anywhere statements are used, including as the contained statement in a
control-flow statement. In that case, I can only make the analogy that this is like
a programming language shooting itself in the foot. For example,

class Test {
public static void main(String[] args) {
if (2+2!=4);
System.out.println("akin to the dangling else problem");
for (int i=0; i>Integer.MAX_VALUE; i++);
System.out.println("better hope this doen't work");
}
}

Executing this program prints

akin to the dangling else problem


better hope this doen't work

The problem is very simply that programmers habitually type the semicolon at
the end of statements, but in the case of control-flow statements such a semico-
lon inadvertently becomes an empty statement. If the continue statement
without label could be used in if-then-else statements, there would be
no need for “empty” statements. I suspect the next programming language

528 JAVA RULES


will break with this insanity. Meanwhile, eternal vigilance is the price of the empty
statement.
All of the control-flow statements, the synchronized statement, and the
try statement can contain other statements. These statements are said to
immediately contain another statement if there are no intervening statements
that also contain it. For example,

public boolean equals(Object obj) {


if (this == obj) return true;
if ((obj != null) && (obj instanceof String)) {
String that = (String)obj;
if (this.count == that.count) {
char v1[] = this.value;
char v2[] = that.value;
int i = this.offset;
int j = that.offset;
for (int i=this.count; i != 0; i--) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}

This is the equals(Object obj) method in the String class (the notori-
ously slow string comparison). The second if-then statement in bold immedi-
ately contains a local variable declaration statement and yet another if-then
statement. It does not immediately contain any of the other local variable decla-
ration statements, the for loop, or either of the first two return statements.
The difference between the terms contains and immediately contains is criti-
cally important in explaining the syntax of the Java programming language.

4.8.1 Control-flow Statements


All control-flow statements either branch or loop. In so doing, they determine
the order in which other kinds of statements are executed. Control-flow state-

EXPRESSIONS, STATEMENTS, AND BLOCKS 529


ments are referred to as either branching or iteration statements. There are
three branching statements:
• if-then statement
• if-then-else statement
• switch statement
There are also three iteration statements, or what are more commonly
referred to as loops:
• for statement
• while statement
• do statement
Most of the time the decision to branch or loop (i.e., the control flow) depends
on the value of a parenthesized Boolean type expression, which can be thought
of as meaning “do or don’t branch” or “do or don’t execute this loop.” If the
expression does not evaluate to the boolean data type a compiler error is
generated. For example,

class Test {
public static void main(String[] args) {
int x = 2 + 2;
if (x = 4)
System.out.println("what's wrong with this picture?");
}
}

This rather common mistake of using = instead of == in the Boolean expression


of a control-flow statement generates the following compiler error:

Test.java:4: incompatible types


found : int
required: boolean
if (x = 4)
^
1 error

The use of Boolean expressions in if-then and if-then-else statements is


very intuitive. If the Boolean expression evaluates to true, the first contained
statement or block is executed. Otherwise, the statement or block contained in

530 JAVA RULES


the else part of the if-then- else statement are executed. Loops continue to
execute until the Boolean expression evaluates to false. All of the following
are therefore infinite loops:

for (;;) { } //preferred over for (;true;) as a matter of style


for (;true;) { }
while (true) { }
do { } while (true); //highly unusual

Note that the for loop has two versions of an infinite loop. The for (;;) and
while (true) infinite loops are by far the most common. The choice is
purely a matter of style. There is no difference whatsoever in the code that is
generated. For example,

class Test {
void forLoop() {
for (;;)
System.out.println("Hello World");
}
void whileLoop() {
while (true)
System.out.println("Hello World");
}
}

The decompile code for both the forLoop() and whileLoop() methods is
identical:

0 goto 3
3 getstatic #2 <Field java.io.PrintStream out>
6 ldc #3 <String "Hello World">
8 invokevirtual #4 <Method void println(java.lang.String)>
11 goto 3

The compiler recognizes all four of these as infinite loops. Consequently, any
statements after the unconditional execution of one of these statements is
unreachable.
There are only two exceptions to the rule that control flow expressions eval-
uate parenthesized Boolean type expressions:

EXPRESSIONS, STATEMENTS, AND BLOCKS 531


• The Boolean expression in a for loop is not parenthesized
• The switch statement evaluates an int type expression
The difference between using a switch statement versus a nested if-then-
else statement is discussed at length in this section, but otherwise the
switch , for, while, and do statements are discussed in the following sub-
sections. The remainder of this section discusses the if-then and if-then-
else statements.
In most of Java Rules, the term if statement means either an if-then or
an if-then-else statement. Only in this chapter do I use the more formal if-
then and if -then-else (as does the JLS). These statements are substantially
different.

An if-then statement conditionally executes the contained state-


ment or block, whereas an if-then-else statement is a binary
branch.

Nested if-then-else statements represent a third use of the if statement as


a multi-way branch in which there is a practically unlimited number of
branches. The switch statement is also a multi-way branch. The difference
between the two is the type of the expression(s) evaluated. Nested if-then-
else statements evaluate Boolean expressions whereas the type of the expres-
sion in a switch statement must be int or one of the smaller integral types.
There is a certain style of coding nested if -then-else statements that is sub-
stantially the same as a switch statement. For example,

if (n == 0)
zero++;
else if (n == 1)
one++;
else if (n == 2)
two++;
else if (n == 3)
three++;
else
four++;

532 JAVA RULES


This style of coding is sometimes referred to as a multi-way if-else. There are
two defining characteristics. The first is coding the else if on the same line.
This solves the problem of runaway indentation when coding deeply nested if-
then-else statements. The other defining characteristic is the final else which
corresponds to the default label in a switch statement. For example, here
is the same code using a switch statement:

switch(n) {
case 0: zero++;
break;
case 1: one++;
break;
case 2: two++;
break;
case 3: three++;
break;
default: four++;
break;
}

So which multi-way branch is faster? The switch statement is thought to be


more efficient than a comparable nested if-then-else statement because it is
implemented using one of two special instructions (either tableswitch or
lookupswitch) which takes advantage of the fact that no two case constants
are the same, but I caution against making too much out of the speed differ-
ence. For example,

import java.util.Random;
class Test {
public static void main(String[] args) {
test1(100000, true);
test2(100000, true);
test1(1000000, false);
test2(1000000, false);
test1(10000000, false);
test2(10000000, false);
test1(100000000, false);
test2(100000000, false);
test1(1000000000, false);
test2(1000000000, false);
}

EXPRESSIONS, STATEMENTS, AND BLOCKS 533


static Random random = new Random();
static int zero, one, two, three, four;
static void test1(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
int n = random.nextInt(5);
if (n == 0)
zero++;
else if (n == 1)
one++;
else if (n == 2)
two++;
else if (n == 3)
three++;
else if (n == 4)
four++;
else
assert false;
}
timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using if-then-else statements " +
timer.getElapsedTime());
}
}
static void test2(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
int n = random.nextInt(5);
switch(n) {
case 0: zero++;
break;
case 1: one++;
break;
case 2: two++;
break;
case 3: three++;
break;
case 4: four++;
break;
default: assert false;
}
}

534 JAVA RULES


timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using switch statement " +
timer.getElapsedTime());
}
}
}

Here is some typical output on my computer:

1000000 using if-then-else statements 218


1000000 using switch statement 203
10000000 using if-then-else statements 2219
10000000 using switch statement 2078
100000000 using if-then-else statements 22188
100000000 using switch statement 20703
1000000000 using if-then-else statements 221562
1000000000 using switch statement 207188

Even at one billion iterations the difference between a switch and nested if-
then-else statements is only 14.374 seconds in an average runtime of approx-
imately two minutes and fifteen second (a difference of about seven percent).
The relationship between the switch statement and a “multi-way if-else” is
comparable to the relationship between the conditional operator and an if-then-
else statement. If you can use a switch statement or the conditional
operator, they are generally preferable to the alternative. In the case of a
switch statement, this is largely a matter of the type of the expression being
evaluated. These are more a matter of style, however, than performance.
This last point begs the question of should switch statements be used if
there is only one or two cases. There are numerous examples of switch state-
ments that have only one or two cases in the core API. Here are some examples
from the Component class:

switch(id) {
case FocusEvent.FOCUS_GAINED:
listener.focusGained(e);
break;
case FocusEvent.FOCUS_LOST:
listener.focusLost(e);

EXPRESSIONS, STATEMENTS, AND BLOCKS 535


break;
}

switch(id) {
case MouseEvent.MOUSE_WHEEL:
listener.mouseWheelMoved(e);
break;
}

These are examples of using switch statements for method dispatch, which is
now generally recognized as a “code smell” (see page 119). When I see exam-
ples such as these I am forced to conclude one of three things:
• The programmer is habituated to the use of switch statements
• Additional case constants are anticipated
• The programmer thinks switch statements are much faster than a compa-
rable if-then or if-then-else statement
In the case of these examples from the Component class, I am inclined to think
it is a combination of being habituated and anticipating additional cases. Both of
these are understandable. It is also understandably a matter of style when the
case constants are stacked, as with the following argument check from the core
API.

switch (orientation) {
case VERTICAL:
case HORIZONTAL:
break;
default:
throw new IllegalArgumentException(
"orientation must be one of: VERTICAL, HORIZONTAL");
}

In this case a comparable if-then statement would not only have to repeat the
name of the orientation argument but would have to be stated negatively:

if (!(orientatin == VERTICAL || orientation == HORIZONTAL))


throw new IllegalArgumentException(
"orientation must be one of: VERTICAL, HORIZONTAL");

536 JAVA RULES


Were the names of the case constants qualified, this would be even more awk-
ward. My reason for addressing the subject of switch statements that have
only one or two cases is the third bulleted item. This should not be done in the
name of efficiency. As shown above, there is no measurable performance differ-
ence. Assuming that the cases are not stacked, a comparable if-then or if-
then-else statement reads much more naturally.
The “Code Conventions for the Java Programming Language” document that
Sun publishes suggest that if statements should always use braces:
The if-else class of statements should have the following form:

if (condition) {
statements;
}

if (condition) {
statements;
} else {
statements;
}

if (condition) {
statements;
} else if (condition) {
statements;
} else {
statements;
}

Note: if statements always use braces {}. Avoid the following error-
prone form:

if (condition) //AVOID! THIS OMITS THE BRACES {}!


statement;41

Note that none of the recommended styles uses indentation. I cannot agree with
with the last point. Sun is not practicing what it preaches. If the contained state-
ment fits on a single line, if-then statements often do not use braces. That

41. Unascribed, §7.4, “if, if-else, if else-if else Statements.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 537


includes numerous examples from the core API. This is more generally true of all
control-flow statements. Here is where to draw the line:

for (int n=0; n<5000; n++)


if ((n & -n) == n)
System.out.println(n);

This code is legal because the contained if-then is a single statement (though it
spans more than one line. Examples such as these are extremely error-
prone, and the use of braces in the for statement should not be regarded as a
matter of style.

4.8.1.1 The switch Statement


The switch statement is sometimes referred to as a case statement or
multi-way branch. All three terms are equally acceptable, except that multi-
way branch could also refer to nested if-then-else statements. The switch
statement has a vocabulary all its own. There is the switch block, switch
block statement groups, switch labels, case labels, case constants, and
the default label. All of these terms are defined in this section.
The general form of a switch statement is as follows.

switch (expression) {
case CASE_CONSTANT: statementGroup;

default: statementGroup;
}

The type of the expression must be char, byte, short, or int. The body of
a switch statement that follows is called a switch block (and is required
syntax). The difference between a switch block and other blocks is that all of
the statements in a switch block are labeled with one or more switch
labels. The term switch label refers to both case labels and the default
label. The statement or statements that follow a switch label are referred to
as a switch block statement group (or statement group for short). State-
ment “groups” are something of a quirk in the syntax of the Java programming
language. Everywhere else “blocks” are used. Actually, a statement group can
be enclosed in a block, but doing so is never required.

538 JAVA RULES


A case label is the case keyword followed by a case constant and then
a colon. Case labels can be stacked. In the following example from the Core API,
the case labels are in bold.

switch(id) {
case KeyEvent.KEY_PRESSED:
case KeyEvent.KEY_RELEASED:
case MouseEvent.MOUSE_PRESSED:
case MouseEvent.MOUSE_RELEASED:
case MouseEvent.MOUSE_MOVED:
case MouseEvent.MOUSE_DRAGGED:
case MouseEvent.MOUSE_ENTERED:
case MouseEvent.MOUSE_EXITED:
case MouseEvent.MOUSE_WHEEL:
case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED:
case InputMethodEvent.CARET_POSITION_CHANGED:
consumed = true;
break;
default:
// event type cannot be consumed
}

This example is formatted exactly as it is in the original source code. Note that
the case labels are indented. The “Code Conventions for the Java Programming
Language” document prefers a different style of coding switch statements:

switch (condition) {
case ABC:
statements;
/* falls through */

case DEF:
statements;
break;

case XYZ:
statements;
break;

default:
statements;

EXPRESSIONS, STATEMENTS, AND BLOCKS 539


break;
42
}

The switch labels are not indented and there is a blank line after each
switch block statement group. Many API programmers respect the former
convention, but few the latter. In fact, examples of switch statements in the
core API that use a blank line after each switch block statement group are
hard to find. Personally, I do not like either convention.
The type of a case constant must be assignment compatible with the type of
the parenthesized expression in a switch statement. For example,

switch (0) {
case 'a':
System.out.println("letter");
break;
}

This compiles because char to int is a widening primitive conversion. The


'a' is treated as if it were the numeric literal 97. At present, the javac com-
piler generates a possible loss of precision compiler error if the type
of the case constant is not assignment compatible with the type of the parenthe-
sized expression. For example,

class Test {
public static void main(String[] args) {
final int i = Byte.MAX_VALUE + 1;
byte b = 0;
switch (b) {
case i:
System.out.println("number");
break;
}
}
}

Attempting to compile this program generates the following compiler error:

Test.java:6: possible loss of precision


found : int

42. Unascribed, §7.8, “switch Statements.”

540 JAVA RULES


required: byte
case i:
^
1 error

Considering that the JLS clearly states that this example should not compile, this
error message is not a very intuitive. It really should be fixed.
Case constants must be either compile-time constant expressions (which is
typically either a character or integer literal) or an inlined constant. The term
inlined constant is defined in 1.5.2 Inlined Constants. Variables (or normal
constants for that matter) specifically cannot be used. That is why they are
called case constants. Furthermore, the value of each case constant must be
unique. For example,

class Test {
public static void main(String[] args) {
switch (0) {
case 'a': //the integral value of which is 97
System.out.println("letter");
break;
case 97:
System.out.println("number");
break;
}
}
}

Attempting to compile this program generates the following duplicate case


label error message:

Test.java:7: duplicate case label


case 97:
^
1 error

The requirement that case constants must be compile-time constant expres-


sions or inlined constants and the reason why variables cannot be used as case
constants are very closely related. The machine instructions used to implement
switch statements require that no two case constants are the same. There-
fore, the compiler must evaluate the case constants to make sure that each has

EXPRESSIONS, STATEMENTS, AND BLOCKS 541


a different value. It then writes the value of the case constant to the class file,
never a variable name. In other words, the value of the case constant is inlined.
This requirement to inline the value of case constants is actually what gave rise
to the more general use of inlined constants in the Java programming language.
The only way to be absolutely sure that case labels have the same value at runt-
ime as they did when compiled is to require that they are either compile-time
constant expressions or inlined constants.
The default label is optional. There can be at most one of them. It is
somewhat natural to code the default label last, but doing so is not required.
Some programmers actually prefer that default is the first label in a switch
block. In fact, when defaulting to a case label (by stacking the default label
on top of the case label) making default the first label in a switch block
should be a programmer convention. Here is an example from the core API:

switch (status) {
default:
case IMAGEERROR:
flags |= ImageObserver.ERROR | ImageObserver.ABORT;
break;
case IMAGEABORTED:
flags |= ImageObserver.ABORT;
break;
case STATICIMAGEDONE:
flags |= ImageObserver.ALLBITS;
break;
case SINGLEFRAMEDONE:
flags |= ImageObserver.FRAMEBITS;
break;
}

This would not read as well if the default label were moved.
A switch statement is executed by first evaluating the expression in paren-
theses. The statements in a switch block are executed beginning at the first
statement following a case constant equal to the value of the expression (some-
times called a matching case label). If there is no matching case label, the
default statement group is executed. If there is neither a matching case label nor

542 JAVA RULES


a default label, the switch statement completes normally without execut-
ing any of the contained statements. For example,

class Test {
public static void main(String[] args) {
test(0);
test(1);
switch (1) {
case 0: System.out.println("nothing prints");
break;
}
}
static void test(int i) {
switch (i) {
case 0: System.out.println("matching case label");
break;
default: System.out.println("default case label");
break;
}
}
}

Executing this program prints

matching case label


default case label

Note that the last statement in each of these statement groups is break. This
is required (except for the last statement group in a switch block) because
once a matching case label is found execution of the statements contained in a
switch block continues until either a control-transfer statement is executed or
the switch block completes normally (by falling through to the closing brace of
the switch block). This somewhat counterintuitive behavior of switch state-
ments is referred to as falling though. For example,

class Test {
public static void main(String[] args) {
int n = 0;
switch (n) {
case 0: System.out.print('H');
case 1: System.out.print('e');
case 2: System.out.print('l');

EXPRESSIONS, STATEMENTS, AND BLOCKS 543


case 3: System.out.print('l');
case 4: System.out.print('o');
case 5: System.out.print(' ');
case 6: System.out.print('W');
case 7: System.out.print('o');
case 8: System.out.print('r');
case 9: System.out.print('l');
case 10: System.out.print('d');
case 11: System.out.print('!');
}
}
}

Executing this program prints Hello World!. Because it is easy to forget to


include a break statement at the end of a statement group, the javac com-
piler includes a non-standard -Xswitchcheck option that checks for fall
though (on all but the last statement group). For example,

class Test {
public static void main(String[] args) {
int x = 0;
switch (x) {
case 1:
System.out.println("one");
break;
case 2:
System.out.println("two");
case 3:
System.out.println("three");
}
}
}

Compiling this program using the non-standard -Xswitchcheck option gener-


ates the following warning:

Test.java:10: warning: possible fall-through into case


case 3:
^
1 warning

It is consider good programming style to include a break statement after the


last statement group even though doing so is not required.

544 JAVA RULES


Curiously, the -Xswitchcheck compiler option does not issue a warning
if the last statement group does not end with a break statement. Technically
this is not necessary, but it is considered good coding practice. Even the JLS
includes a comment that a break in the last statement group is “not needed,
but good style.”43 Can you guess why? The answer is fall through. If a careless
maintenance programmer were to add a new case to the bottom of the
switch statement, the unnecessary break might become a life saver. The
“Code Conventions for the Java Programming Language” document says:
Every switch statement should include a default case. The break in
the default case is redundant, but it prevents a fall-through error if
later another case is added.44

The suggestion to always include a default label is more a matter of style,


but forgetting to include a break in the last statement group has potentially
serious consequences.
If fall through is intentional, a fall-through comment is considered required
documentation. It assures the reader that fall through is intentional and not an
oversight. Here is an example of a fall-through comment in a switch statement
from the core API:

switch (type) {
case '1':
default:
formattedNum = String.valueOf(itemNum);
break;

case 'A':
uppercase = true;
// fall through
case 'a':
formattedNum = formatAlphaNumerals(itemNum);
break;

case 'I':
uppercase = true;

43. Gosling et al., §14.10, “The switch Statement.”


44. Unascribed, §7.4, “if, if-else, if else-if else Statements.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 545


// fall through
case 'i':
formattedNum = formatRomanNumerals(itemNum);
}

The switch statement is often criticized for falling though. Exiting the switch
block after executing the statement group of the matching case label (or the
default label, as the case may be) would be more intuitive, but considerably
less flexible from a language design perspective.

4.8.1.2 The for Statement


The for statement has a header and a body. There are three parts to a for
statement header are shown in Figure 4.5. The Boolean expression is in the
middle. The other two parts are somewhat clumsily referred to as ForInit and
ForUpdate in the grammatical productions of 14.13 The for Statement in the
JLS. I have adopted these names in Java Rules, but no longer italicize them.

Figure 4.5 The three parts of a for statement header.

The ForInit part of the for statement header must be either a comma-separated
list of top-level expressions or a local variable declaration statement. These
are mutually exclusive options (which I will explain momentarily). The ForUp-

546 JAVA RULES


date part of the for statement header is one or more comma-separated top-
level expressions. Top-level expressions are defined in 4.2.2 Expression State-
ments and Other Top-Level Expressions.
If a top-level expression is coded in the ForInit part of a for statement
header, then a local variable declaration statement cannot be coded, and vice
versa. For example:

class Test {
public static void main(String[] args) {
int j;
for (int i=0, j=0; ; ) //COMPILER ERROR
continue;
}
}

Attempting to compile this program generates the following compiler errors:

Test.java:4: j is already defined in main(java.lang.String[])


for (int i=0, j=0; ; ) //COMPILER ERROR
^
1 error

This example shows why a local variable declaration statement cannot be mixed
with top-level expressions. If that were the case, j=0 could be either a declara-
tor or assignment expression. Here are some for statement headers that do
compile:

//top-level expressions
int i, j=0;
for (i=0, j++, System.out.println("starting loop");;)

//multiple local variable declarators


for (int k=0, x=0, y=0;;)

The top-level expression can be any expression in Table 4.2 Expressions that
Produce Side Effects on page 402.
The local variable declaration statement may have multiple declarators, but
the fact that there can only be one local variable declaration statement means
that the type of all the variables declared will necessarily be the same. In the
example above, three int type variables are declared.

EXPRESSIONS, STATEMENTS, AND BLOCKS 547


The loop control variable does not have to be an integer, however. Any type
variable can be used, including boolean or reference types. For example:

class Test {
public static void main(String[] args) {

for (boolean b = false; b == false; ) {


System.out.println("boolean");
b = true;
}

for (char ch = 'a'; ch < 'z'; ch++) {


System.out.print(ch);
}
System.out.println();

for (String s = "start" ; s.equals("start");) {


System.out.println("String");
s = "stop";
}
}
}

Executing this program prints:

boolean
abcdefghijklmnopqrstuvwxy
String

Figure 4.6 is a graphical representation of a for loop. Code in the ForInit


part of the header is executed outside of the loop. Each iteration of a for loop
begins by evaluating the Boolean expression. If the Boolean expression evalu-
ates to false, the loop is immediately exited. Otherwise the loop body and For-
Update part of the header are executed before beginning the next iteration.
It is possible to write a for statement that does all of the work in the
header. Here is an example from the java.awt.GridBagLayout class:

/* Adjust the grid width and height */


for (px = curX + curWidth; r.width < px; r.width++);
for (py = curY + curHeight; r.height < py; r.height++);

Here is another example that is used in several core API classes:

548 JAVA RULES


Figure 4.6 A for loop

ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());

Can you tell what this code is doing? Both of these examples execute an empty
statement. This style of coding is also recommended in the “Code Conventions
for the Java Programming Language” document:
An empty for statement (one in which all the work is done in the initial-
ization, condition, and update clauses) should have the following form:

for (initialization; condition; update);

[end of quote]45

I take exception with this, however. In all likelihood, such constructs will generate
a compiler warning in some future release of the Java programming language

45. Unascribed, “Code Conventions for the Java Programming Language” available online at
java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html, (Mountain View: Sun Micro-
systems, 1995-1999), §10.5.1, “Parentheses.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 549


(assuming that compiler warning are made part of the specification). There are
two alternatives to this style of coding:

for (initialization; condition; update)


continue;

for (initialization; condition; update) {


//NO-OP
}

Either of these are preferable to executing an empty statement.


The for statement is sometimes referred to as a determinate loop
because the Boolean expression usually (but not always) establishes the exact
number of iterations to execute the loop. This makes the for statement ideal
for iterating over the elements of fixed-length arrays. Here is the canonical form
of a for loop used to iterate over the elements of an array:

for (int i=0; i < array.length; i++) {



}

For example,

class Test {
public static void main(String[] args) {
int[] placeValues = new int[8];
placeValues[0] = 1;
for (int i = 1; i < placeValues.length; i++)
placeValues[i] = placeValues[i-1] << 1;
int total = 0;
for (int i = 0; i < placeValues.length; i++) {
total += placeValues[i];
System.out.println(placeValues[i]);
}
System.out.println();
System.out.println(total == Byte.MAX_VALUE +
Math.abs(Byte.MIN_VALUE));
}
}

Executing this program prints

550 JAVA RULES


1
2
4
8
16
32
64
128

true

The question is sometimes asked: Should the field access expression in a


loop control mechanism such as i < placeValues.length be taken
out of the loop? For example,
int length = placeValues.length;
for (int i = 1; i < length; i++)
placeValues[i] = placeValues[i-1] << 1;

This is a special case of a controversial performance optimization known as


using stack variables (read local variables). The short answer is No.
Microbenchmark tests below show that the saving is so infinitesimally small that
for the average loop it is practically immeasurable.
Bloch’s “Item 29: Minimize the scope of local variables” could be interpreted
as supporting the idea that accessing the length field in an array should be
removed from the loop:
Similar idioms exist for other looping tasks, for example,

for (int i = 0, n = expensiveComputation(); i < n; i++) {


doSomething();
}

Again, this idiom uses two loop variables, and the second variable, n, is
used to avoid the cost of performing redundant computation on every
iteration. As a rule, you should use this idiom if the loop test involves a
method invocation and the method invocation is guaranteed to return
the same result on each iteration.46

46. Bloch, “Item 29: Minimize the scope of local variables.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 551


It is tempting to think of array.length as expensive operation. If that were
the case, the canonical form of the for loop would look like this:

for (int i=0, n=array.length; i<n; i++) {



}

In this example, n could be considered a stack variable, but Bloch does not actu-
ally suggest the use a different idiom for iterating over the elements of an array.
In fact, in an unrelated item he specifically says that “the standard idiom for loop-
ing through an array does not necessarily result in redundant checks; some
modern JVM implementations optimize them away.”47 Microbenchmark tests can
always put such issues to rest. For example,

class Test {
static int nLoops;
byte[] array0=new byte[nLoops],array1=new byte[nLoops],
array2=new byte[nLoops],array3=new byte[nLoops],
array4=new byte[nLoops],array5=new byte[nLoops],
array6=new byte[nLoops],array7=new byte[nLoops],
array8=new byte[nLoops],array9=new byte[nLoops];

public static void main(String[] args) {


nLoops = 5;
new Test().test1();
new Test().test2();
nLoops = 6;
new Test().test1();
new Test().test2();
nLoops = 7;
new Test().test1();
new Test().test2();
nLoops = 8;
new Test().test1();
new Test().test2();
nLoops = 9;
new Test().test1();
new Test().test2();
nLoops = 10;
new Test().test1();

47. Bloch, “Item 39: Use exceptions only for exception conditions.”

552 JAVA RULES


new Test().test2();
}
void test1() {
long count = 0;
Microbenchmark timer = new Microbenchmark().start();
for (int i0=0; i0<array0.length; i0++)
for (int i1=0; i1<array1.length; i1++)
for (int i2=0; i2<array2.length; i2++)
for (int i3=0; i3<array3.length; i3++)
for (int i4=0; i4<array4.length; i4++)
for (int i5=0; i5<array5.length; i5++)
for (int i6=0; i6<array6.length; i6++)
for (int i7=0; i7<array7.length; i7++)
for (int i8=0; i8<array8.length; i8++)
for (int i9=0; i9<array9.length; i9++)
count++;
timer.stop();
System.out.println(count +
" using field access expressions: " +
timer.getElapsedTime());
}

void test2() {
long count = 0;
Microbenchmark timer = new Microbenchmark().start();
for (int i0=0, n0=array0.length; i0<n0; i0++)
for (int i1=0, n1=array0.length; i1<n1; i1++)
for (int i2=0, n2=array0.length; i2<n2; i2++)
for (int i3=0, n3=array0.length; i3<n3; i3++)
for (int i4=0, n4=array0.length; i4<n4; i4++)
for (int i5=0, n5=array0.length; i5<n5; i5++)
for (int i6=0, n6=array0.length; i6<n6; i6++)
for (int i7=0, n7=array0.length; i7<n7; i7++)
for (int i8=0, n8=array0.length; i8<n8; i8++)
for (int i9=0, n9=array0.length; i9<n9; i9++)
count++;
timer.stop();
System.out.println(count + " using stack variables: " +
timer.getElapsedTime());
}
}

EXPRESSIONS, STATEMENTS, AND BLOCKS 553


This program starts at almost ten million iterations and ends at ten billion, for a
total of almost fifteen billion iterations. Here is some typical output:

9765625 using field access expressions: 109


9765625 using stack variables: 78
60466176 using field access expressions: 531
60466176 using stack variables: 531
282475249 using field access expressions: 2375
282475249 using stack variables: 2360
1073741824 using field access expressions: 8750
1073741824 using stack variables: 8734
3486784401 using field access expressions: 27844
3486784401 using stack variables: 27797
10000000000 using field access expressions: 78484
10000000000 using stack variables: 78563

As you can see, there is no difference. Unless you are working on the kind of
intensive graphics application that gave birth to Duff’s Device, I would not even
contemplate these kinds of optimizations.

4.8.1.3 The do and while Statements


The do and while statements are very simple loops that have only one differ-
ence: the do statement will always execute at least once because the Boolean
expression is evaluated after the loop executes. Figure 4.7 is a graphical repre-
sentation of both loops. If you have a hard time remembering which iteration
statement always executes at least once, just remember that “the do statement
will always do something.”
The do and while statements are sometimes called indeterminate
loops. Indeterminate loops are used to do things like read files for which the
exact number of iterations to be executed is not always known. The fact that the
body of a do statement is guaranteed to execute at least once is the sole basis
for deciding when to use a do statement versus a while statement. In prac-
tice, while statements are much more common than do statements.

4.8.2 Labeled Statements


Before discussing the break and continue statements, labeled statements
must be discussed. A label is an identifier followed by a colon (much like the

554 JAVA RULES


Figure 4.7 A do and while loop

default label in a switch statement). The statement that is immediately


contained by the label is referred to as a labeled statement. Arbitrary blocks
can also be labeled. The general form of a labeled statement is

Identifier: StatementOrBlock

If the labeled statement is a for, while, or do statement, the label is some-


times referred to as a loop label.
Because the context in which a label is used is so markedly different from
that of any other entity, a label can have the same name as any package, class,

EXPRESSIONS, STATEMENTS, AND BLOCKS 555


interface, method, field, local variable, or parameter that is in scope (i.e., any
other kind of named entity). Labels can also be reused much like local variable
names can be reused, except the term “scope” does not apply to labels.
Instead, the JLS says:
A statement labeled by an identifier must not appear anywhere within
another statement labeled by the same identifier, or a compile-time
error will occur. Two statements can be labeled by the same identifier
only if neither statement contains the other.48

It’s always interesting to study the changes Gilad Bracha made in the Second
Edition of the JLS. He changed this paragraph to read as follows.
Let l be a label, and let m be the immediately enclosing method, con-
structor, instance initializer or static initializer. It is a compile-time error
if l shadows the declaration of another label immediately enclosed in
m.49
For example, the following loops do not compile because the continue target
is ambiguous.50

loop: while (expression) {



loop: while (expression) {

if (statement)
continue loop; //COMPILER ERROR

}

}

The labels must have unique identifiers:

outerloop: while (expression) {



innerloop: while (expression) {

48. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification (Reading: Addison-
Wesley, 1996), §14.6, “Labeled Statements.” (Do not update.)
49. Gosling et al., §14.7, “Labeled Statements.”
50. Because of a bug in the javac compile, this example compiled in Java 1.1 and earlier
releases. See Bug Id 1241001.

556 JAVA RULES



if (statement)
continue outerloop;

}

}

This rule contributes significantly to making break and continue statements


with labels bulletproof.
Some programmers think that a break or continue statement with
no label is an accident waiting to happen. The target for a continue state-
ment with no label is “the innermost enclosing while, do, or for state-
ment.”51 Likewise, the target for a break statement with no label is “the
innermost enclosing switch, while, do, or for statement.”52 The problem
is that the “innermost enclosing” switch, while, do, or for statement does
not necessarily immediately contain the break or continue statement. For
example,

class Test {
public static void main(String[] args) {
int[] array = {1,2,3};
for (int i=0; i < array.length; i++) {
if (2+2==4) {
synchronized (Test.class) {
try {
continue;
}
catch(Throwable e) { }
}
}
}
}
}

As this example shows, it is easy for a break or continue statement with no


label to become significantly removed from the “innermost enclosing” switch,

51. Gosling et al., The Java Language Specification, §14.15, “The continue Statement.”
52. Gosling et al., The Java Language Specification, §14.14, “The break Statement.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 557


for, while, and do statement. This is a serious maintenance issue because
adding another switch, for, while, or do statement can inadvertently
change the existing break and continue targets.
Such an accident happened on January 15, 1990, when AT&T's long-distance
Electronic Switching System (ESS) crashed because of an untested software
patch. An if statement was replaced by a switch statement. Unfortunately,
the if statement contained a break that was intended to exit the “innermost
enclosing” loop. It must have been a very large if statement because the main-
tenance programmer apparently did not even see the break statement. Conse-
quently, after the change was completed the same break statement exited the
switch , not the loop. A disaster ensued. The outage lasted for only nine hours,
but cost the company an estimated $60 to $75 million dollars in lost reve-
nues. In a Time magazine article written a few days later, then AT&T Chairman
Robert Allen said: “It was the worst nightmare I’ve had in 32 years in the busi-
ness.”53
Most programmers do not use labels in simple loops that have only a few
lines of code. The switch statement is interesting in this regard. Practically no
one uses a break with label in switch statements. This is understandable as
a matter of style for the same reason that a continue or break in a simple
loop may seem like overkill. Nevertheless, it should be pointed out that it is

In part because labels cannot be shadowed, a break or


continue statement with label is bulletproof.

impossible to inadvertently change the target to a break or continue state-


ment with label. Perhaps the rule should be that if the “innermost enclosing”
switch , for, while , or do statement does not immediately contain the
break or continue statement, a label should be used. If this simple rule
had been followed at AT&T (which actually was not possible because C doesn’t
have labels), the disaster could have been avoided.

53. Philip Elmer-Dewitt, “Ghost in the Machine,” Time Magazine, January 29, 1990.

558 JAVA RULES


One of the curiosities about the Java programming language is that almost
any statement can be labeled. In fact, the only statements (or rather quasi state-
ments) that cannot be labeled are local variable and local class declaration
statements. Here it is important to understand that labeled statements are either
break targets or continue targets, nothing else. That is their only use. A
continue target must be a for, while, and do statement (because only
loops are continued). For example,

class Test {
public static void main(String[] args) {
block: {
while (true)
continue block;
}
}
}

Attempting to compile this program generates the following compiler error:

Test.java:5: not a loop label: block


continue block;
^
1 error

The continue statement explains why iteration statements can be labeled.


Any control-flow statements, synchronized statements, or try statements
as well as arbitrary blocks can be labeled because any statement that can
contain other statements can be a break target. That leaves only control-
transfer statements, expression statements, and the empty statement. For
example,

class Test {
public static void main(String[] args) {
label: return;
}
}

Why does this program compile? Labeling a control-transfer statement such as


return is completely useless. If control-transfer statements, expression state-
ments, and the empty statement cannot contain other statements, they cannot

EXPRESSIONS, STATEMENTS, AND BLOCKS 559


be break targets, so why can they be labeled? There is no explanation for this
in the JLS.

4.8.3 Control-transfer Statements (a.k.a. Abrupt Completion)


There are four control-transfer statements in the Java programming language.
• break statement
• continue statement
• return statement
• throw statement
The throw statements is discussed in 6.7 The throw Statement. The
return statement is discussed in 1.6.2 Result Types and the return State-
ment. The break and continue statements are discussed in the following
subsections.
As the title of this section suggests, execution of any of these control-trans-
fer statements is by definition abrupt completion. In fact, the JLS has the fol-
lowing to say about the four control-transfer statements:
A break, continue, return, or throw statement cannot com-
plete normally.54

The use of abrupt completion in reference to one of the four control-transfer


statements should not be confused with the abnormal termination of a pro-
gram. It is not the program that is completing abruptly. Abrupt completion refers
either to the evaluation of an expression or to the execution of a statement, both
of which have a normal mode of execution “in which certain computational
steps are carried out.”55 If all the steps are carried out, the expression or state-
ment is said to complete normally. The opposite of normal completion is
abrupt completion, which again refers to expressions and statements. Expres-
sions complete abruptly for only one reason; namely, the throwing of an excep-
tion. If an expression completes abruptly, so do any statements that are

54. Gosling et al., §14.20, “Unreachable Statements.”


55. Gosling et al., §14.1, “Normal and Abrupt Completion of Statements” and also §15.6, “Normal
and Abrupt Completion of Evaluation.”

560 JAVA RULES


executing at the time. Otherwise, the only statements that complete abruptly
are the four control-transfer statements.
Now for one of my lapses into terminological excess. The adverb abruptly
should always follow the verb. For example,
A method invocation expression that completes because of an excep-
tion causes transfer of control to a point outside the method is said to
complete abruptly.56
This terminology is summarized in Table 4.12.

Table 4.12 Execution Mode Terminology


Execution Mode Noun Verb

normal normal completion complete normally

abrupt abrupt completion complete abruptly

In discussions of the System.exit(int status) method, however, the


phrase abnormal termination is preferred.

4.8.3.1 The continue Statement


Only the for, while, and do iteration statements can contain a continue
statement. A continue statement not contained in one of these three iteration
statements will generate a compiler error.
Every continue statement has a continue target as well as a loop-
continuation point. The continue target is the for, while , and do state-
ment that is to be continued. In the case of a continue statement without
label, the continue target is the innermost enclosing for, while, and do
statement. In the case of a continue statement with label, the continue
target is labeled for, while, and do statement.
The loop-continuation point (a JLS term)57 is the same for all three itera-
tion statements. It is always the bottom of the loop body. In a do loop, this is

56. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, §2.16, “Exceptions.”
57. Gosling et al., §14.15, “The continue Statement.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 561


immediately before the Boolean expression is evaluated. See Figure 4.7, “A do
and while loop” on page 555. In a for loop, it is immediately before the For-
Update part of the for loop header. See Figure 4.6, “A for loop” on page 549.

4.8.3.2 The break Statement


Every break statement has a break target. The break target for break
without label is the innermost enclosing switch, for, while, or do state-
ment. Except for the addition of the switch statement, this sounds very much
like the continue statement so far. There is a huge difference, however. The
break target for a break with label can be any statement that can con-
tain other statements (more specifically, any statement that can contain the
break statement). This includes arbitrary blocks. That makes it a much more
general purpose control-transfer statement in comparison to a break without
label or a continue statement. The following is a complete list of statements
that contain other statements:
• iteration statements (for, do, while)
• if-then and if-then-else statements
• switch statement
• try statements
• synchronized statements
These are all of the control-flow statements as well as the try and
synchoronize statements. As noted in 4.8.2 Labeled Statements, these are
the only statements that should ever be labeled because they are the only state-
ments that can be break or continue targets. To this list must be added
arbitrary blocks. For example,

class Test {
public static void main(String[] args) {
block:
{
System.out.println("in block");
if (true)
break block;
System.out.println("does not print");

562 JAVA RULES


}
System.out.println("out of block");
}
}

Executing this program prints

in block
out of block

Iteration statements, deeply nested if-then-else statements, and arbi-


trary blocks are the most common break targets.
The following program includes examples of each statement that can be a
break target.

class Test {
public static void main(String[] args) {

int i = 0, j = 0;
boolean b = true;

label: while (true) {


System.out.println("loops");
if (b)
break label;
System.out.println("does not print");
}

label:
if (i == 0)
if (j == 0) {
System.out.println("nested if statements");
if (b)
break label;
System.out.println("does not print");
}

label:
{
System.out.println("arbitrary blocks");
if (b)
break label;
System.out.println("does not print");

EXPRESSIONS, STATEMENTS, AND BLOCKS 563


}

label:
synchronized(Test.class) {
System.out.println("switch statements");
if (b)
break label;
System.out.println("does not print");
}

label:
try {
System.out.println("try statements");
if (b)
break label;
System.out.println("does not print");
}
catch (Exception e) {}

label:
switch (i) {
case 0:
switch (j) {
case 0: System.out.println("switch statements");
if (b)
break label;
}
System.out.println("does not print");
}
}
}

Executing this program prints:

loops
nested if statements
arbitrary blocks
switch statements
try statements
switch statements

As stated in 4.8.2 Labeled Statements, simple loops and switch statements


are not usually labeled.

564 JAVA RULES


Technically speaking, when a labeled break statement is executed, control
is transferred to the labeled statement, which is then said to “immediately com-
plete normally.”58 That means control is transferred to the next sequential state-
ment after the labeled statement. There is something counterintuitive about this.
Looking at the source code for a break statement, the transfer of control is to
a point above the break statement, but the next sequential statement to be
executed is below the break statement. For example, Figure 4.8 is the source
code for searching a three dimensional array.

Figure 4.8 The control flow in a labeled break statement is counterintuitive

The continue statement suffers from a similar counterintuitive problem.


It is interesting to note that breaking out of nested loops (as shown in Figure
4.8), or what is sometimes called a multi-level break, is what the break

58. Gosling et al., §14.14, “The break Statement.”

EXPRESSIONS, STATEMENTS, AND BLOCKS 565


statement was originally designed for. Historically, programmers have used
goto statements to break out of nested loops. Even structured programmers
who studiously avoided the use of goto statements could justify their use on
these grounds. According to studies referenced in The Java Language Envi-
ronment: A White Paper, the break and continue statements in Java
accomplish what “roughly 90 percent” of goto statements did in the C pro-
gramming language:
Java has no goto statement. Studies illustrated that goto is
(mis)used more often than not simply “because it's there”. Eliminating
goto led to a simplification of the language--there are no rules about
the effects of a goto into the middle of a for statement, for example.
Studies on approximately 100,000 lines of C code determined that
roughly 90 percent of the goto statements were used purely to obtain
the effect of breaking out of nested loops. 59

The break statement has uses other than breaking out of nested loops, but it is
helpful to understand the history behind this statement. It is really highly con-
strained goto statement, as is throwing an exception.

4.9 Blocks
A block is one or more statements within braces. Table 4.13 lists all of the
places where blocks are required syntax. In addition to these blocks, most con-

Table 4.13 Blocks that are Required Syntax


Where Blocks are Used Name of Blocka

Class or interface declarations Class or interface body

Method and constructor declarations Method and constructor body

Array initializers Array initializers

static initialization block static initializers


Instance initialization block Instance initializers

59. James Gosling and Henry McGilton, The Java Language Environment: A White Paper,
§2.2.5, “No More Goto Statements.”

566 JAVA RULES


Table 4.13 Blocks that are Required Syntax
Where Blocks are Used Name of Blocka

try statement try block


catch clause of a try statement catch block (or exception handler)
finally clause of a try statement finally block
switch statement switch block
synchronized statement synchronized block
a. I refer to both instance and static initializers as initialization blocks in Java Rules. See 3.2 The stat-
ic Modifier in Volume 1 for an explanation why.

trol-flow statements can execute either a single statement of a block of state-


ments.60 Such blocks are sometimes referred to as a compound statement.
Finally there are arbitrary blocks that are neither required syntax nor
replacements for contained statements. For example,

class Test {
public static void main(String args[]) {
{
String s = "This is a completely arbitrary block";
System.out.println(s);
}
}
}

The term arbitrary block is of my own making, but one that I feel is important
and very useful is discussing the language. One of the primary uses of arbitrary
blocks is as break or continue targets. The following example from 4.8.3.2
The break Statement is repeated here for your convenience:

class Test {
public static void main(String[] args) {
block:
{

60. Surprisingly, the JLS does not explicitly say so. I know because I have diligently searched for
such a statement in both editions of the JLS. That a block can always be substituted for a statement
is implicit in the “grammatical productions” of 14.5 Statements and again in 18.1 The Grammar of
the Java Programming Language.

EXPRESSIONS, STATEMENTS, AND BLOCKS 567


System.out.println("in block");
if (true)
break block;
System.out.println("does not print");
}
System.out.println("out of block");
}
}

Executing this program prints

in block
out of block

When an arbitrary block is labeled, it is referred to as a labeled block. There


are no restrictions whatsoever against the use of arbitrary blocks. As noted in
the section on the switch statement, even a switch block statement group
can be placed in a block.
The issue of placing semicolons after blocks is very confusing. It should not
be. Blocks in and of themselves never require a semicolon. If the block
happens to be the last thing in a statement, then and only then is a semicolon
required. Two examples come immediately to mind. Array initializers can only be
used in variable initializers, which means they are almost always followed by a
semicolon that ends the variable declaration (unless there are multiple variable
declarators). Anonymous classes are another example. If a class instance cre-
ation expression for an anonymous class is used to initialize a variable, then a
semicolon follows the body of the anonymous class body for the same reason
that a semicolon follows an array initializer. Otherwise, semicolons after blocks
are never required. They are sometimes described as extraneous, but that is
misleading.
Strictly speaking, there is no such thing as an extraneous semicolon in the
grammar of the Java programming language. A semicolon in a block of code is
an empty statement. Outside of a block a code, a semicolon is a javac bug
that has been around so long that Sun decided to amend the specification rather
than fix the bug, which would break an untold number of classes in production
(including even some classes in the core API). Here are examples of the bug:

568 JAVA RULES


class Test {

static { };
{ };

int i = 0;;

void method() { };

};

This program compiles even though only the first semicolon after the field decla-
ration is legal according to the original JLS. The main Bug Id is 4057172 (dated
June 6, 1997), the evaluation for which says:
This is a bug. The relevant productions are in JLS 19.8.3 et al. Proba-
bly there should be a release with warning messages prior to making
these hard errors, since these typos are likely to be somewhat wide-
spread.

xxxxx@xxxxx 1997-06-06

The TRC has determined that due to the widespread use of extra semi-
colons in practice, it is more desirable to modify the spec to allow their
use than to break that much existing code. I'm changing this bug over
to the specification subcategory so that the grammar will be updated in
the next version of the JLS.

xxxxx@xxxxx 1997-07-1561

This really is a problem for “write once, compile anywhere,” albeit a minor one.
For example, the jikes compiler will issue a warning if semicolons are used
incorrectly. The Second Edition of the JLS does not address this issue so there
exists the possibility that Sun plans to eventually fix this bug in the javac com-
piler.

61. See Bug Id is 4057172.

EXPRESSIONS, STATEMENTS, AND BLOCKS 569


570 JAVA RULES
Chapter 5

Type Conversions and


the Cast Operator

Chapter Contents
5.1 Introduction 572
5.2 The Type of a Variable or Expression versus the Class of an Object 573
5.2.1 The Phrase “type of an object” is in Prevalent Use 576
5.2.2 The Term class type is Where Everything Goes Afoul 577
5.3 Java is a Strongly Typed Language 583
5.4 Substitution is a Higher Concept than Polymorphism 587
5.5 Forbidden Conversions 595
5.6 Permitted Conversions 597
5.6.1 Identity Conversions 600
5.6.2 Primitive Type Conversions 601
5.6.2.1 Widening Primitive Conversions 604
5.6.2.2 Narrowing Primitive Conversions 605
5.6.3 Reference Type Conversions 612
5.6.3.1 Widening Reference Conversions 614
5.6.3.2 Narrowing Reference Conversions 619
5.7 Conversion Contexts 623
5.7.1 Simple Assignment Conversion Context 627
5.7.1.1 The ArrayStoreException 631
5.7.2 Method Invocation Conversion Context 632
5.7.3 Method Return Conversion Context 636
5.7.4 The Cast Operator 638
5.7.5 The Implicit Cast in a Compound Assignment Operation 643
5.8 Overloaded Method Matching 644
5.8.1 Choosing The Most Specific Applicable Method 645
5.8.2 The Declaring Class of Applicable Methods 650
5.9 Value Set Conversions 656

TYPE CONVERSIONS AND THE CAST OPERATOR 571


5.1 Introduction
This chapter is all about types. The term data type is defined in 4.2 The Defini-
tion of Data Types in Volume 1. This chapter expands on that definition to include
the difference between class and type. The meaning of “Java is a strongly typed
language” (type checking) and polymorphism (“many types”) as well as type con-
versions and the cast operator are also discussed.
The chapter begins with a detailed discussion of the difference between the
type of a variable or expression versus the class of an object, which
includes a subsection entitled “The Phrase ‘type of an object’ is in Prevalent Use”
in which I make critical comments about some incorrect usage found in the First
Edition of The Java Programming Language.1 In reference to that section,
one of the technical reviewers of this book commented that I was “flogging a well-
beaten horse carcass.” While I can appreciate the humor in that remark, I cannot
agree with the sentiment that the section in question is overkill. One of the
authors of The Java Programming Language is Dr. Gosling. It is therefore
essential reading for every Java programmer. I would never be without a copy. In
fact, I have all three editions on my bookshelf. It is precisely because the authors
are held in such high esteem, that I feel free to constructively criticize their
usage. The motivating factor for writing the aforementioned section can be found
in the About This Book section of the Preface under “Correct Usage is Strongly
Emphasized.” Moreover, one of the three quotes is no longer in The Java Pro-
gramming Language, Third Edition, and the other two have been corrected.
Having said this, if the reader still thinks that I am flogging a well-beaten horse
carcass, then I am doing so because I want to make sure it is dead.

1. Ken Arnold and James Gosling, The Java Programming Language , (Boston, Addison-Wesley,
1996). If anything is merciless, it is quoting from the first edition of a book that is now in the third
edition. Still I think it is important to show that even the “Father of Java” can make this mistake (or at
least one of his coauthors).

572 JAVA RULES


5.2 The Type of a Variable or Expression versus the
Class of an Object
The type of a variable is declared, and the type of other expressions can be
deduced at compile-time. In fact, type is a compile-time concept. If the type of
a variable or expression is a reference type and the value is not null, then the
variable or expression references an object on the heap. That object has a
class, not a type.

The phrase “the type of an object” is by definition incorrect usage,


as is run-time type and run-time type information (RTTI), no matter
how well established the usage.

The term run-time type and the initialism RTTI are holdovers from the C pro-
gramming language which is not object-oriented and does not have classes.
The First Edition of the JLS was unremitting in its effort to differentiate the use of
the terms type and class, as can be seen in the following quote from a section
named “Variables Have Types, Objects Have Classes.”
Every object belongs to some particular class: the class that was men-
tioned in the creation expression that produced the object, the class
whose class object was used to invoke the newInstance method to
produce the object, or the String class for objects implicitly created
by the string concatenation operator +. This class is called the class of
the object … An object is said to be an instance of its class and of all
superclasses of its class.

(Sometimes a variable or expression is said to have a ‘runtime type’ but


that is an abuse of terminology; it refers to the class of the object
referred to by the value of the variable or expression at run time,
assuming that the value is not null. Properly speaking, type is a com-
pile-time notion. A variable or expression has a type; an object or
array has no type, but belongs to a class.)
The type of a variable is always declared, and the type of an expression
can be deduced at compile time. The type limits the possible values
that the variable can hold or the expression can produce at run time. If

TYPE CONVERSIONS AND THE CAST OPERATOR 573


a runtime value is a reference that is not null, it refers to an object
or array that has a class (not a type), and that class will necessarily
be compatible with the compile-time type [of the variable or expression
that references it].2 [emphasis added]

There is no other place in all of the literature on object-oriented programming


where you will find such a “line in the sand” argument for the difference between
the type of a variable or expression and the class of the object referenced. One
comes to expect this level of precision in usage from the JLS. It is a rare piece
of programming documentation in that respect.
In one of the few changes Gilad Bracha made in the Second Edition that I
think was truly regrettable, the name of this section was changed and the lan-
guage softened in an apparent concession to the widespread use of the term
run-time type. He then added the following two-sentence paragraph.
Sometimes a variable or expression is said to have a “run-time type”.
This refers to the class of the object referred to by the value of the vari-
able or expression at run time, assuming that the value is not null.3

I implore the authors to restore the original language. At most this concession to
incorrect usage should have been added as a footnote. I don’t mind saying so
because there are numerous other places in Java Rules in which I praise the
added clarity of the Second Edition.
Type is a compile-time concept that relates to variables and other expres-
sions. Once a program is running, the objects created have classes. According
to Design Patterns:
It’s important to understand the difference between an object’s class
and its type.4

2. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, (Reading: Addi-
son-Wesley, 1996), §4.5.5, “Variables Have Types, Objects Have Classes.” (Do not update.) By the
way, I believe this is the longest quote in all of Java Rules. I am very grateful to Sun Microsystems
and in particular to Lisa Friendly and Doug Krammer for liberal permission to quote from Sun
sources (which includes the specifications as well as the Java Series).
3. Gosling et al., §4.5.6, “Types, Classes, and Interfaces.”
4. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns, Design Pat-
terns: Elements of Reusable Object-Oriented Software, (Reading: Addison-Wesley, 1995), 16.

574 JAVA RULES


I regard this as something of an understatement. The ability of a programmer
to differentiate between the compile-time type of a variable or expression
and the class of an object referenced at run time is of fundamental impor-
tance to understanding the Java programming language (or any other
object-oriented programming language for that matter) as a whole. But the
reader should beware that the difference between type and class is not univer-
sally appreciated. Consider the following quote from Thinking in Java:
…although what we really do in object-oriented programming is to cre-
ate new data types, virtually all object-oriented programming languages
use the “class” keyword. When you see the word ‘type,’ think ‘class’
and vice versa.5

It would be better to say “when you see the word ‘type’ think ‘compiler’ and when
you see the word ‘class’ think ‘Java Virtual Machine.’” This is not intended as a
criticism of Thinking in Java. A footnote on the very same page states that
“some people make a distinction, stating that type determines the interface
while class is a particular implementation of that interface,”6 which is exactly the
difference between type and class. That distinction can be seen in the following
quotes from the UML Notation Guide:
Classes implement types. A type provides a specification of external
behavior. A class provides an implementation data structure and a pro-
cedural implementation of methods that together implement the speci-
fied behavior.7

A type establishes a behavioral specification for classes. A class that


supports the operations defined by a type is said to implement the
type…8

The Thinking in Java statement that “when you see the word ‘type,’ think
‘class’ and vice versa” is to be commended as a very apt characterization of
what most programmers actually do think, either because they have concluded

5. Bruce Eckel, Thinking in Java, 2nd Edition, Release 11 (Upper Saddle River: Prentice Hall,
2000), 33.
6. Ibid.
7. UML Notation Guide, Version 1.0, (Santa Clara: Rational Software, 1997), §4.6, “Type.”
8. Ibid. §4.6.1, “Semantics.”

TYPE CONVERSIONS AND THE CAST OPERATOR 575


that type and class are synonymous, or because they have never really thought
about it.

5.2.1 The Phrase “type of an object” is in Prevalent Use


The phrase “type of an object” is incorrect because objects do not have a type.
As in all matters of terminology I defer to the JLS which (at least in the First Edi-
tion) is quite clear about this. Such phrases should be automatically branded as
incorrect usage. Surprisingly, the worst examples of usage I have ever encoun-
tered were in the First Edition of The Java Programming Language. In that
book, sentences like the following can be found:
Objects in Java have a type; that type is the object’s class. 9

And again in the same chapter:


For all other references you use, invoking a method uses the actual
type of the object, not the type of the object reference.10

It were better had the writer said


For all other references you use, invoking a method uses the actual
class of the object, not the type of the object reference.
Here is one last example:
Abstraction is helpful when some of the behavior is true for most or all
objects of a given type, but some behavior makes sense only for partic-
ular types of objects, and not a general superclass. 11

Our intent here is not to single out The Java Programming Language for crit-
icism; the idea is to show that even the best software engineers and technical
writers can and do make this mistake. The awkwardness of the incorrect usage
in the last quote is evident in the change from “type” to “superclass” in “some
behavior makes sense only for particular types of objects, and not a general

9. Ken Arnold and James Gosling, The Java Programming Language, (Reading: Addison-Wes-
ley, 1996), §1.6, “Classes and Objects.” (Do not update.)
10. Ibid. §1.10.2, “Invoking Methods from the Superclass.”
11. Ibid. §3.7, “Abstract Classes and Methods.”

576 JAVA RULES


superclass.” Now let us correct the usage in this passage, and see how much
better this passage reads:
Abstraction is helpful when some of the behavior is true for most or all
objects of a given class, but some behavior makes sense only for spe-
cific subclasses of objects, and not a general superclass.

How is the average programmer supposed to learn the difference between type
and class when phrases such as “type of an object” are in prevalent use?

5.2.2 The Term class type is Where Everything Goes Afoul


Most of the confusion about the terms class and type is in the term class type.
If classes and types are different, what is a “class type”? Is it a class? Or is it a
type? Every once in a while a software engineer or technical writer will grab this
bull by the horns. For example,
Object-oriented development puts a great emphasis on the difference
between interface and implementation, but this is often overlooked in
practice because the notion of class in an OO language combines both
interface and implementation.12

This quote is from UML Distilled. Design Patterns addresses the class type
problem as follows:
Of course, there’s a close relationship between class and type.
Because a class defines the operations an object can perform, it also
defines the object’s type. When we say that an object is an instance of
a class, we imply the object supports the interface defined by the
class.13

It would have been much better to have said, “it also defines one of the object’s
types” (the class type). To fully understand the answer to this question you must
understand the newer definition of a type in object-oriented programming. This

12. Martin Fowler, UML Distilled: Applying the Standard Object Modeling Language, (Read-
ing: Addison-Wesley, 1997), 55.
13. Erich Gamma et al, 17.

TYPE CONVERSIONS AND THE CAST OPERATOR 577


definition was first given in 4.2.2 The Object-Oriented Definition of Type in Vol-
ume 1. It is repeated here for your convenience.

A type consists of an identifier (the name of the type), API docs (the
specification) and an interface. Thus an interface type is the purist
form of a type.

The Unified Modeling Language (UML) makes a science out of terminology. The
following definitions from the UML glossary fully support this definition of type.
type

A description of a set of instances that share the same operations,


abstract attributes and relationships, and semantics. A type may define
an operation specification (such as a signature) but not an operation
implementation (such as a method). Usage note: Type is sometimes
used synonymously with interface, but it is not an equivalent term.

class

A description of a set of objects that share the same attributes, opera-


tions, methods, relationships, and semantics. A class is an implementa-
tion of type.

interface

The use of a type to describe the externally visible behavior of a class,


object, or other entity. In the case of a class or object, the interface
includes the signatures of the operations.

implementation

A definition of how something is constructed or computed. For exam-


ple, a class is an implementation of a type, a method is an implementa-
tion of an operation.14

14. UML Semantics, Appendix M1-UML Glossary, version 1.0 dated 13 January 1997 (Santa
Clara: Rational Software, 1997), Glossary.

578 JAVA RULES


I included the UML definition of implementation to emphasize that “a class is an
implementation of a type.”14 These definitions can be tabulated to make then
perfectly clear. Table 5.1 is just such a table.

Table 5.1 Differences in Critical Object-Oriented Terms


Type

API Docs
Terms Identifier (the spec) Interface Implementation

Class ✔
Interface ✔
Interface type ✔ ✔ ✔
Class type ✔ ✔ ✔ ✔

The public methods in a class type implicitly define an interface. That


interface has a name and API docs; hence it is referred to as a type. The API
docs are interesting in this regard. They only show the interface implicitly
defined by a class type. By that I mean they do not include the default access
and private members of the class. The remainder of this section further
explains the differences (or rather the lack thereof) between interface and class
types.
When designing an object-oriented system, it may be helpful to begin by
declaring only interface types.15 If necessary, they can later be converted to
class types merely by changing the interface keyword to class and then

15. Actually, the best way to design a system is to write the API docs first. What I am talking
about here is actually writing the documentation comments in a source code (or .java ) file and
then automatically generating the API docs using javadoc. This iterative process should continue
until there is general agreement on all of the types involved (whether they be class or interfaces
types) and their behavior. Using this approach to system design means that all of the methods in a
class type will have either an empty method body or a return statement that does nothing but
quiet the compiler (such as return 0 in a method that has a result type of int). I am utterly
convinced that this is “rapid development” at its best. It yields excellent API docs that have
been thoroughly reviewed, makes the job of the application programmer much simpler, and also
makes it possible to control the kind of design changes that creep into a system after development
and unit testing.

TYPE CONVERSIONS AND THE CAST OPERATOR 579


providing an implementation. For example, here is the interface implicitly defined
by the Object class.

public interface Object {


Class getClass();
int hashCode();
boolean equals(Object obj);
String toString();
void notify();
void notifyAll();
void wait(long timeout) throws InterruptedException;
void wait(long timeout, int nanos) throws InterruptedException;
void wait() throws InterruptedException;
}

There is nothing tricky about this. Separating out the interface implicitly defined
by a class type follows a very specific formula. Typically only the method sig-
natures and throws clauses of the public instance methods are
included. If there are any public instance variables or inner member classes,
however, they too would be included. Here is another example,

public interface Thread


void start();
void interrupt();
boolean isInterrupted();
void destroy();
boolean isAlive();
void setPriority(int newPriority);
int getPriority();
void setName(String name);
String getName();
ThreadGroup getThreadGroup();
void join(long millis);
void join(long millis, int nanos) throws InterruptedException;
void join() throws InterruptedException;
void setDaemon(boolean on);
boolean isDaemon();
void checkAccess();
ClassLoader getContextClassLoader();
void setContextClassLoader(ClassLoader cl);
}

580 JAVA RULES


This is the interface implicitly defined by the Thread class (excluding depre-
cated methods). Note that constructors, methods declared in superclasses (in
this case, the Object class), and the run() method from the Runnable
interface are not included in this imaginary interface version of the Thread
class type.
All of the following are therefore part of the implementation that comes
after the interfaces are designed.
• All nested top-level classes and interfaces
• All class variables and class methods
• Non-public inner member classes as well as all local
and anonymous classes
• Non-public instance variables and instance methods
• The implementation of public methods
• Variable initializers
• Initialization blocks
• Constructors
Nested top-level classes and interfaces, class variables, and class methods are
excluded because the essential meaning of interface is to interface with
instances of the class (read send messages to objects), not static code.
The implementation of public methods is excluded for obvious reasons. Vari-
able initializers, initialization blocks, and constructors are excluded because the
initialization of fields is part of the implementation. (Arguably, public construc-
tors are part of the interface.) The point is to understand the difference between
interface and implementation in class types.
That a type is either a class type or an interface type only matters in class
instance and array creation expressions, because types that do not include an
implementation (i.e., interface types) cannot be instantiated. (Anonymous
classes blur even this critical distinction between class and interface types.) All
other uses of type names do not distinguish between class and interface types.
To make this perfectly clear, Table 5.2 is a complete list of every use of type
names in the Java programming language. As you read this table, ask yourself,
does it matter if the type involved is a class or interface type? The answer is no

TYPE CONVERSIONS AND THE CAST OPERATOR 581


Table 5.2 How Types are Used
Uses Examples

Class As type names in class literals double.class


Literals

Qualified As reference types in qualified Math.PI


Names names

As class types in qualified Math.class


this and super keywords
Type As type names in an explicit return (PC)comp[i];
Checking cast operation

As type names in an if (comp[i] instanceof PC)


instanceof operation
Type As reference types in an public final class
Declarations extends clause Integer extends Number
implements Comparable
As interface types in an
implements clause
As reference types in single- import java.util.Random;
type-import declarations

Method and As result types in method public String toString()


Constructor declarations
Declarations

Variable As variable types in local Date today;


Declarations variable and field declarations

As parameter types in method public static void


and constructor declarations main(String[] args)
Exception As Throwable types in a void doSomething()
Handling throws clause throws Exception
As Throwable subtypes in catch (Exception e) { }
catch clauses
Class and As class types in class instance Random r = new Random(10);
Array creation expressions
Creation
As array types in array creation int[] array = new int[10]
expressions

582 JAVA RULES


in all but three cases: qualified this and super keywords are always qualified
by class names [emphasis on class]; the throws clause in a method or con-
structor declaration as well as the type name in a catch clause of a try state-
ment must be a Throwable type, and most importantly class instance and
array creation expression must name a class type (with the notable exception of
anonymous classes).

5.3 Java is a Strongly Typed Language


Java is said to be a strongly typed language. Another term for this is type dis-
cipline. The sheer strength of the language used by Patrick Naughton in the fol-
lowing quote from the now out-of-print The Java Handbook should give the
reader an idea of the importance placed on type discipline by the designers of
the Java programming language.
Part of Java’s safety and robustness come from the fact that Java is a
strongly typed language. Every expression has a type. Every variable
has a type and every type is strictly defined. All assignments of expres-
sions to variables, whether explicit, or via parameter passing in method
calls, are checked for type compatibility. There are no automatic coer-
cions or conversions of conflicting types as in some languages. The
Java compiler statically checks all of the code it compiles to ensure
that the types are correct. Type mismatches are not compiler warn-
ings, they are errors which must be corrected before the compiler will
finish compiling the class.16 [emphasis added]

I would be hard pressed to provide a better definition of the term strongly


typed language. The two most important defining characteristics are that
every expression that denotes a value has a type and the compiler uses
those types to perform extensive type checking (which is often referred to as
static type checking). Expressions that denote a variable are only checked
to the extent that the variable cannot be declared final. A Java compiler
enforces type discipline for expressions that denote nothing (a method invoca-

16. Patrick Naughton, The Java Handbook, (Berkeley: McGraw-Hill, 1996), Chapter 4,
“Types.”

TYPE CONVERSIONS AND THE CAST OPERATOR 583


tion expression that invoke a method declared void) by making sure they are
only used as top-level expressions, where no type is required.
Naughton fails to mention one very important aspect of type discipline. The
following two run-time “type checks” are an integral part of the type system in
the Java programming language (in particular the guarantee that the class of the
object referenced is assignment compatible with the type of the variable or
expression that references it).
• Narrowing reference conversions require that the class of the object (not
the “type of the object”) referenced at run time is assignment compati-
ble with the type name in the cast operator. See 5.6.3.2 Narrowing Refer-
ence Conversions for a discussion.
• If the component type of an array is either a class or interface type, all
assignments to the unnamed variables in that array are automatically
checked at run time. The class of the object (not the “type of the object”)
referenced at run time must be assignment compatible with the compo-
nent type of the array. This automatic run-time “type check” is discussed in
5.7.1.1 The ArrayStoreException.
Note that in both cases these run-time “type checks” involve reference types.
For the primitive data types, all type checking is performed by the compiler. No
run-time “type checks” are required because the primitive types “always hold a
primitive value of that same type.” 17 As annoying as it may be, I consistently
use quotation marks around “type checks” in my work when it is really
the class of the object referenced at run time that is being checked.
These are not really “type checks” per se. Notably, the JLS uses “run-time
check” in the following specification, making a conscious decision to leave out
the “type”.
A variable is a storage location and has an associated type, sometimes
called its compile-time type, that is either a primitive type or a refer-
ence type. A variable always contains a value that is assignment com-
patible with its type…

17. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java Language Specification, Sec-
ond Edition, (Reading: Addison-Wesley, 1996-2000), §4.2, “Primitive Types and Values.”

584 JAVA RULES


Compatibility of the value of a variable with its type is guaranteed by
the design of the Java language. Default values are compatible and all
assignments to a variable are checked for assignment compatibility,
usually at compile time, but, in a single case involving arrays, a run-
time check is made.18 [emphasis added]
Properly speaking these are “run-time class checks,” but no one is willing to go
down that path.
Type checking minimizes program bugs. The classic example of compile-
time type checking in the Java programming language is the Boolean expression
in an if statement. For example:

class Test {
public static void main(String[] args) {
int i = 0;
if (i = 0)
System.out.println("doubtless a typo");
}
}
Attempting to compile this program generates the following compiler error:

Test.java:4: incompatible types


found : int
required: boolean
if (i = 0)
^
1 error

Any compiler error message that include the phrase incompatible types
or inconvertible types is the result of compile-time type checking. Using
the simple assignment operator = instead of the equality operator == in Boolean
expressions is a common source of program bugs in the C and C++ program-
ming languages. In the Java programming language, however, statements such
as if (x = 0) will not compile because the type of the expression x = 0 is
numeric, and control flow expressions must evaluate to the boolean data
type. This is just one of many type checks performed at compile time. I know

18. Ibid., §4.5, “Variables.”

TYPE CONVERSIONS AND THE CAST OPERATOR 585


this sounds corny, but “type checking is your friend.” Type checking is a system-
atic effort at the language level to keep programmers from making mistakes
that can be easily detected based on the type of a variable or expression. It is
not flawless, however. Consider the following example,

class Test {
public static void main(String[] args) {
boolean a = false, b = true;
if (a = b)
System.out.println("probably a typo");
}
}

This compiles because the type of the assignment expression a = b is boolean.

NOTE 5.1
The following section is the culmination of a relentless effort spanning
several years to clarify the definition of polymorphism in Java Rules.
Doing so has required differentiating overriding from polymorphism.
Overriding is not the same as polymorphism, but there is a very
close relationship between the two. While a given class of objects
may appear to have “many forms,” all of the objects must behave the
same. This is precisely why dynamic method lookup (or what C pro-
grammers call virtual functions) is considered “the normal method
dispatch in the Java programming language”19 (or any other object-ori-
ented programming language for that matter).

19. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, Second Edition,
(Boston: Addison-Wesley, 1999), §3.11.8, “Method Invocation and Return Instructions.” The industry
standard terminology for dynamic method lookup is late binding and overriding.

586 JAVA RULES


5.4 Substitution is a Higher Concept than
Polymorphism
Most programmers know the etymology of polymorphism from the Greek
polys meaning “many” and morph meaning “form.” The definition of polymor-
phism is another matter. There are actually three distinct kinds of polymorphism
in computer programming:
• Parametric polymorphism (parameterized types)
• Ad-hoc polymorphism (operator overloading)
• Normal polymorphism in an object-oriented programming language
The problem does not stop there. There are two fundamentally different defini-
tions of “normal” polymorphism. One emphasizes widening reference conver-
sions (for example, assigning the value of a GregorianCalendar type
variable to a Calendar type variable). The other emphasizes method overrid-
ing (or dynamic method lookup). This subtle difference contributes significantly
to making the term polymorphism as complicated as it sounds. I address this
problem by differentiating polymorphism from the concept of substitution. This
results in the narrowest possible definition of polymorphism, but one that
achieves absolute clarity. Then I briefly explain what parametric and ad-hoc poly-
morphism mean to Java programmers.
Some software engineers and technical writers emphasize method overrid-
ing (or dynamic method lookup) as the essential meaning of polymorphism,
sometimes to the exclusion of widening reference conversions. Here are some
representative quotes:
An object’s ability to decide what method to apply to itself, depending
on where it is in the inheritance hierarchy, is usually called polymor-
phism. The idea behind polymorphism is that while the message may
be the same, objects may respond differently…20 [Note that software

20. Gary Cornell and Cay S. Horstmann, Core Java, (Upper Saddle River: Prentice Hall, 1997),
148. As always, please do not interpret this as a personal criticism of the authors. I make it a rule
to only (constructively) criticize software engineers and technical writers for whom I have
a deep and abiding respect. In this case, I assure you that certainly does apply to the authors of
Core Java and Thinking in Java. These are two of the finest Java books on the market. In fact, I
was thrilled to learn that Bruce Eckel even mentioned me in his Preface. I will be always grateful to
him for helping to get me started in Java.

TYPE CONVERSIONS AND THE CAST OPERATOR 587


engineers and technical writers who define polymorphism in terms of
overriding are susceptible of making the gross assumption that the
classes involved are related. More on this momentarily.]

When you send a message to an object even though you don’t know
what specific type it is, and the right thing happens, that’s called poly-
morphism. The process used by object-oriented programming lan-
guages to implement polymorphism is called dynamic binding.21

Such a definition is wrong, however. It completely ignores the Greek meaning of


the word polymorphism, which is different forms (read types). You simply can-
not define polymorphism in terms that ignore the essential meaning of the term.
The Greek “morph” in polymorphism equates with type. Therefore, polymor-
phism or “many forms” is another way of saying that there are “many types” for
any given class of objects. An object changes “form” (again, read type) when it
is assigned to a different type variable in what are defined as widening refer-
ence conversions . As explained in 5.6.3.1 Widening Reference Conversions,
the other type is necessarily either a superclass or superinterface.
The section name “Substitution is a Higher Concept than Polymorphism” is a
conscious decision on my part to put method overriding (or dynamic method
lookup) on an equal footing with polymorphism in order to “make room” if you
will for the higher concept of substitution. Otherwise, in addition to the problem
of defining polymorphism, there is the difficulty of explaining how the concept of
substitution (such as the Liskov Substitution Principle) relates to polymorphism.
An instance of a subtype can be substituted for an instance of a supertype
because subtypes inherit the interface of supertypes. Therefore, any message

21. Bruce Eckel, Thinking in Java, (Upper Saddle River: Prentice Hall, 1998), 30. This quote
involves an incorrect use of type. Objects do not have a type. They have a class. It should say,
“even though you don’t know what specific class of object it is.”

588 JAVA RULES


sent to the supertype can also be sent to a subtype. Figure 5.1 shows the rela-
tionship between polymorphism, overriding, and substitution.

Figure 5.1 The relationship of polymorphism and overriding to substitution

Sometimes a subclass only overrides methods inherited from the direct


superclass, and does not declare any new methods. This is called pure inherit-
ance or pure substitution, because the superclass and subclass have the
exact same interface. Anonymous classes that implicitly extend other classes
are an important example of pure substitution in the Java programming lan-
guage. Methods can certainly be added to an anonymous class, even public
methods, but they cannot be invoked using the class or interface type to which
the object is assigned. Hence, they have essentially the same interface as their
superclass. It is worth noting that in some schools of thought pure substitution is
the only form of inheritance that should be supported in an object-oriented pro-
gramming language. The Java programming language, however, supports
“extending” a class by either adding new methods, overriding superclass meth-
ods, or some combination of both adding new methods and overriding.
The following program includes examples of all three concepts.

class Test {
public static void main(String[] args) {
Baseclass base = new Baseclass();
DerivedClass derived = new DerivedClass();

/* two examples of polymorphism */


Baseclass[] array = {base, derived};
base = derived;

/* two examples of substitution */


for (int i=0; i < array.length; i++) {
array[i].doSomething();
}
base.doSomething();

TYPE CONVERSIONS AND THE CAST OPERATOR 589


}
}

/* an example of method overriding */


class Baseclass {
void doSomething() {
System.out.println("Baseclass");
}
}
class DerivedClass extends Baseclass {
void doSomething() {
System.out.println("Derivedclass");
}
}

Without both polymorphism and method overriding there is no substitution. This


is precisely why the virtual invocation mode is considered “normal” in an
object-oriented programming language such as Java.
Using this narrow definition means that polymorphism (or many types) is the
result of interface inheritance and is purely a compile-time concept. Every class
except Object implements at least two types, usually more. The programmer
determines the so-called form of the object by selecting one of those types. The
point at which the programmer in effect says “I want to use this type” or “I want
to use that type” is anytime an instance method is invoked. The type of the vari-
able or expression used as the target reference (a.k.a. the qualifying type) is
either declared or can be deduced at compile time. If you are not sure how to
“deduce” the type of the variable or expression, see Table 1.7 The Five General
Forms and Qualifying Types on page 196.
If polymorphism is to be defined in terms of types, however, then interface
types must certainly be included. A very common and serious flaw in the defini-
tion of polymorphism is to assume that polymorphic classes are related. (The
term related classes as defined in 3.4 The Definition of Related Classes has a
very specific meaning.) Doing so overlooks perhaps the single most important
use of polymorphism; namely, disparate (read unrelated) classes that imple-

590 JAVA RULES


ment the same interface type such as Runnable. Assignments to interface

The matter-of-fact assumption that polymorphism involves related


classes is a gross mistake.

type variables are also widening reference conversions. This is no less polymor-
phism than assignments involving class types. A subclass object can always be
substituted for a superclass object because of interface inheritance. Likewise,
any object that implements a particular interface type can be substituted for any
other object that implements the same interface type.
The following summarizes everything said thus far in this section.
Polymorphism is Greek for “many forms.” The “many forms” of an
object are the “many types” that a class of objects implements. The
qualifying type of a method invocation expression determines the
“form” of the object referenced in the polymorphic (many forms)
sense. Any object that is an instanceof that type can be substi-
tuted at run time as the target object in the same method invocation
expression. The code executed will be appropriate for the given
class of objects because of method overriding (or dynamic
method lookup). This explains why virtual is the normal invocation
mode in an object-oriented programming language such as Java. The
class of the object substituted is often related through a class hierar-
chy (i.e., a subclass), but if interfaces are involved the substituted class
is just as likely to be unrelated.

There is one sentence in this summary that I believe to be at the very heart of
understanding not only polymorphism, but the whole of the Java programming
language:
The qualifying type of a method invocation expression determines the
“form” of the object referenced in the polymorphic (many forms) sense.

TYPE CONVERSIONS AND THE CAST OPERATOR 591


The term qualifying type is defined in 1.9 Qualifying Type versus Compile-Time
Declaration. Consider the Computer class hierarchy in Figure 5.2.

Figure 5.2 The Computer Class Hierarchy

Five different types can be used to reference a PC class object:


Object
Computer
Microcomputer
PC
Networkable (an interface)
Figure 5.3 is a graphical presentation of polymorphism. If the type of the vari-
able in which the object reference is stored is different from the class of the
object referenced, the effect is like that of blinders on a race horse in that
only part of the object interface can be accessed. Why is that? The answer
is simply that the type of the compile-type declaration determines the “form” of
the object referenced in the polymorphic (many forms) sense. All that really

592 JAVA RULES


Figure 5.3 The Many Forms of a PC Object

TYPE CONVERSIONS AND THE CAST OPERATOR 593


means is that you cannot invoke an instance method that does not exist in the
class or interface type that the compiler searched (the qualifying type as deter-
mined in Table 1.7 The Five General Forms and Qualifying Types on page 196).
Otherwise you will get a “cannot resolve symbol” compiler error. It
really is that simple.
Having now defined normal polymorphism in an object-oriented program-
ming language, I want to briefly address parametric and ad-hoc polymorphism.
Neither of these really exist in the Java programming language as of this writing.
The parametric in parametric polymorphism is the adjective form of param-
eters . This is merely a reference to parameterized types (or Generic Java) as
discussed in 6.5 Untyped References versus Parameterized Types in Volume 1.
The term ad-hoc polymorphism refers to operator overloading, but is often
confused with method overloading. The idea here is that operators (or more pre-
cisely the operations they represent) are like methods. Different operand types
result in substantially different operations such as integer versus floating-point
math when using any of the arithmetic operators or addition versus string con-

Method overloading is not ad-hoc polymorphism, or any other kind of


polymorphism for that matter.

catenation when using the + operator. This is what is meant by ad-hoc polymor-
phism, not method overloading. No less than Patrick Naughton got this wrong
in The Java Handbook:
It is possible and often desirable to create more than one method with
the same name, but different parameter lists. This is called method
overloading. Method overloading is used to provide Java’s polymorphic
behavior… 22 [emphasis added]

I would characterize the last statement in this quote as nothing less than bizarre.
No doubt this led to some other Java books making the same mistake. The rea-
son I say Java does not really support ad-hoc polymorphism is that there is no

22. Patrick Naughton, The Java Handbook, 115.

594 JAVA RULES


user-defined operator overloading (thank God, or rather Gosling) as in the C,
C++, and now C# programming languages.

5.5 Forbidden Conversions


Some type conversions are not permitted. Such conversions are referred to as
forbidden conversions in the JLS.23 Forbidden conversions can be thought of
in terms of the type conversion boundaries represented by the double lines in
Figure 5.4. The rules for converting to and from the types within a box are

Figure 5.4 Type Conversion Boundaries

stated briefly in small type. The double lines mean that there are no permitted
conversions to or from the types in that box (with the notable exception of
Object and null for reference types). The most important type conversion

23. Gosling et al., §5.1.7, “Forbidden Conversions.”

TYPE CONVERSIONS AND THE CAST OPERATOR 595


boundary is between reference types and the primitive types. There are no per-
mitted conversions from reference types to primitive types, or vice versa. Within
the primitive types, there are no permitted conversions between numeric types
and the boolean type, or vice versa. That means the only permitted conver-
sion involving the boolean type is an identity conversion (discussed below).
For reference types, there is a type conversion boundary around array types,
with an exception of a conversion from any array type to the Cloneable inter-
face type.
A forbidden conversion involving reference types is essentially meaningless,
like converting two unrelated classes. For example,

Supercomputer cray = new Supercomputer();


PDA newton;
newton = (PDA) cray; //COMPILER ERROR

The reference type conversion newton = (PDA) cray does not compile
because a Supercomputer is not a PDA. More precisely, when a Super-
computer object is created on the heap, none of the instance variables
declared in the PDA class are allocated. Therefore, a Supercomputer object
cannot possibly support the PDA interface. The two classes are unrelated
though part of the same class hierarchy. This explains why the only permitted
conversions between class types are those between related classes. Any of the
reference types can be converted to the Object type. Likewise, the special
null type can be assigned to any reference type. Nothing can be converted to
the special null type, because null is not really a type.
There is only one forbidden conversion between interface types. A method in
one of the interfaces cannot have the same method signature but a different
result type as a method in the other interface because no one class could imple-
ment both interfaces. For example,

class Test {
public static void main(String[] args) {
A a = null;
B b = (B) a; //COMPILER ERROR
}
}

596 JAVA RULES


interface A {
void doSomething();
}

interface B {
int doSomething();
}

Attempting to compile this program generates the following compiler error:

Test.java:5: interfaces A and B are incompatible; both define


doSomething(), but with different return type
B b = (B) a; //COMPILER ERROR
^
1 error

This example compiled prior to the 1.2 release because of Bug Id 4028359.
There is a significant difference between forbidden conversions and per-
mitted conversions that are unsafe. A cast operator is required in a permitted
conversion that can result either in a loss of information or throw a
ClassCast-Exception at run time (which is the very definition of an
unsafe type conversion). However, a cast operator such as (PDA) in the
newton = (PDA) cray example above is a forbidden conversion and does
not compile. Nothing a programmer can do will ever make forbidden conver-
sions compile.

5.6 Permitted Conversions


All type conversions are either widening or narrowing. A narrowing conversion
is any type conversion that potentially could result either in a “loss of informa-
tion”24 or a ClassCastException being thrown at run time. All other type
conversions are widening. In type conversion discussions, a “loss of informa-
tion” always refers to primitive data types. Likewise, the only conversions that
can potentially throw a ClassCastException are reference type conver-
sions.

24. Gosling et al., §5.1.3, “Narrowing Primitive Conversions.”

TYPE CONVERSIONS AND THE CAST OPERATOR 597


Primitive type numeric conversions make an important distinction between a
loss of magnitude and a loss of precision. More precisely, the loss of infor-
mation that can result from a narrowing primitive type conversion is defined as
either a loss of magnitude or a change in the sign bit. Both of those are different

The definition of a “loss of information” in the JLS specifically does


not include the loss of precision in floating-point types because float-
ing-point types are designed to lose precision.

than a loss of precision. All integer to floating-point conversions are categorized


as widening primitive conversions because there is never a loss of magnitude or
a change in the sign bit. The following three widening primitive conversions, how-
ever, have the potential for a loss of precision.
int to float
long to float
long to double
The importance of these three type conversions is such that the discussion of
them was moved from this chapter to Chapter 4, “Primitive Data Types and
Object ” in Volume 1.
The essential meaning of “narrowing” is that a type conversion is unsafe. The
JLS does not use the terms safe and unsafe to describe type conversions, how-
ever. Perhaps it should. These terms are much more explicit. The choice of “nar-
rowing” and “widening” is evidently a reference to the length of primitive data
types. Thus a byte to int type conversion is “widening” because a four-byte
int is wider than a single byte. Likewise, a double to float type conver-
sion is “narrowing” because both the exponent and significand in a float have
fewer bits than in a double. This terminology breaks down, however, in the
case of byte to char type conversions which are superficially widening but
categorized as “narrowing” because they are unsafe. Likewise, short to char
and char to short type conversions are superficially neither widening nor nar-
rowing, but are categorized as “narrowing” because they are unsafe. This

598 JAVA RULES


explains why I prefer to use the terms safe type conversion and unsafe type
conversion as synonyms for widening and narrowing conversions, respectively.
The meaning of “widening” and “narrowing” is entirely different in reference
type conversions. In reference type conversions, these terms do not refer to the
data format (which is usually a pointer as defined by the host operating system).
They could not because all reference types have the same format. Instead these
terms have a polymorphic meaning. The “widest” reference type is the Object
type because a variable or expression of type Object can reference any object
on the heap. More generally, superclasses are wider than subclasses because a
superclass type variable can reference any subclass object. Interface types are
also thought of as being “wide” because they potentially can reference many dif-
ferent classes of objects.
The “narrowest” reference types are those at the bottom of a class hierar-
chy. A variable whose type corresponds to a class at the bottom of a class hier-
archy can only reference objects of that class. For example, a PC type variable
can only reference a PC object. Were the PC class to be extended, then it would
be possible for a PC type variable to reference an object of a different class.
In addition to being either widening or narrowing, all type conversions are
either implicit or explicit. Implicit type conversion are best thought of as auto-
matic conversions. They do not require the use of a cast operator because the
type conversion is (supposed to be) safe. In the JLS, implicit conversions in fact
are always safe. This means there are no less than three synonyms that can be
used to describe any type conversion (other than an identity or string conver-
sion). Table 5.3 lists all of them. I sharply disagree with the JLS, however, in

Table 5.3 Synonyms in Type Conversion Terminology


Descriptions in both the
Type Conversion Properties JLS and Java Rules

The potential for either a loss of information Narrowing


or throwing a ClassCastException Unsafe
at run time Explicit

All other type conversions including the three Widening


primitive type conversions that have the Safe
potential for a loss of precision Implicit

TYPE CONVERSIONS AND THE CAST OPERATOR 599


defining all implicit type conversions as safe. That is an oversimplification that
ignores the potential for a loss of information when using compound assignment
operators. There is an example of this in 5.7.5 The Implicit Cast in a Compound
Assignment Operation. With the exception of compound assignment operators,
unsafe type conversions always require the use of a cast operator. The cast
operator is what makes a type conversion explicit. It forces a programmer to
acknowledge that a type conversion is unsafe, as if saying to the compiler, “I
know what I am doing.” The cast operator is also used in identity conversions,
but unsafe type conversions that require the use of a cast operator should not
be confused with identity conversions that do not. Identity conversions are dis-
cussed in the following subsection.

5.6.1 Identity Conversions


As stated in the specification for identity conversions below, “every expression is
subject to conversion,”25 even something as straightforward as int i = 0. The
type of the integer literal 0 is int, so this is an int to int type conversion.
Such a type conversion is referred to as an identity conversion.
The use of a cast operator in an identity conversion is not unlike the unary
plus operator. It does nothing. As stated in the JLS:
A conversion from a type to that same type is permitted for any type.

This may seem trivial, but it has two practical consequences. First,
it is always permitted for an expression to have the desired type to
begin with, thus allowing the simply stated rule that every expression is
subject to conversion, if only a trivial identity conversion. Second, it
implies that it is permitted for a program to include redundant cast
operators for the sake of clarity. 25

No conversion is required because the type of the cast expression is that same
as the type in the cast operator. In the following example, the declared type of
Math.PI is double. The (double) cast operator is little more than docu-
mentation to a compiler.

25. Gosling et al., The Java Language Specification, §5.1.1, “Identity Conversions.”

600 JAVA RULES


double d = (double) Math.PI;

Such a conversion is said to “identify” the type of the cast expression. The fol-
lowing example is not an identity conversion:

int i = 0;
double d = 0;
d = (double) i

Although int is automatically converted to double in all contexts, the type of


the cast expression is not the same as the type in the cast operator. The
(double) cast operator does not in fact identify the type of the cast expres-
sion. The programmer is merely telling the compiler to do something it already
knows must be done; namely, to use the i2d machine instruction to convert the
int to a double.

5.6.2 Primitive Type Conversions


The terms widening primitive conversion and narrowing primitive conver-
sion always refer to primitive numeric types, because the boolean type can-
not be converted except in an identity conversion. The narrowing primitive
conversions are listed in the first column of Table 5.4. The corresponding widen-
ing primitive conversion is listed in the second column deemed safe. In two
cases, both type conversions are categorized as narrowing. The machine

Table 5.4 Primitive Numeric Conversions


Narrowing Primitive Conversions Widening Primitive Conversions

char to byte ( uses i2b )


byte to char ( uses i2c )

short to byte ( uses i2b ) byte to short ( uses i2s )


short to char ( uses i2c )
char to short ( uses i2s )
int to byte (i2b)a byte to int ( no conversion )
int to short (i2s)a short to int( no conversion )

int to char (i2c)a char to int ( no conversion )

TYPE CONVERSIONS AND THE CAST OPERATOR 601


Table 5.4 Primitive Numeric Conversions (Continued)
Narrowing Primitive Conversions Widening Primitive Conversions

long to byte ( uses l2i and then i2b ) byte to long ( uses i2l )

long to short ( uses l2i and then i2s ) short to long ( uses i2l )

long to char ( uses l2i and then i2c ) char to long ( uses i2l )

long to int (l2i) int to long (i2l)


float to byte ( uses f2i and then i2b ) byte to float ( uses i2f )

float to short( uses f2i and then i2b ) short to float ( uses i2f )

float to char ( uses f2i and then i2c ) char to float ( uses i2f )

float to int (f2i) int to float (i2f)


float to long (f2l) long to float (l2f)
double to byte( uses d2i and then i2b ) byte to double ( uses i2d )
double to short ( uses d2i and then i2s ) short to double ( uses i2d )
double to char( uses d2i and then i2c ) char to double ( uses i2d )
double to int (d2i) int to double (i2d)
double to long (d2l) long to double (l2d)
double to float (d2f) float to double (f2d)
a. Unless the int is a compile-time constant expression in the range of the byte, short, or char, in which
case there is no conversion. Such conversions are referred to a implicit narrowing conversions. They are
discussed in 5.7.1 Simple Assignment Conversion Context below.

instructions in parentheses pop a primitive numeric type value off the operand
stack, convert it to a different data type, and then push the converted value back
on the operand stack. The three instructions marked “no conversion” do not
require a type conversion because the computational type of a byte, short,
or char is int. The nine narrowing type conversions that use two machine
instructions are referred to as two step conversions. They are discussed in the
bottom half of 5.6.2.2 Narrowing Primitive Conversions below.

602 JAVA RULES


Widening primitive conversions are never performed if the value to be
converted is a compile-time constant expression. This is true in all conver-
sion contexts. For example,

class Test {
public static void main(String[] args) {
float f = Byte.MAX_VALUE;
test(Short.MAX_VALUE);
f = 0.0f + Character.MAX_VALUE;
f = (float) Integer.MAX_VALUE;
}
static float test(float f) {
return 0L;
}
}

The decompiled code for the main and test(float f) methods follows.

Method void main(java.lang.String[])


0 ldc #2 <Real 127.0>
2 fstore_1
3 ldc #3 <Real 32767.0>
5 invokestatic #4 <Method float test(float)>
8 pop
9 ldc #5 <Real 65535.0>
11 fstore_1
12 ldc #6 <Real 2.14748365E9>
14 fstore_1
15 return

Method float test(float)


0 fconst_0
1 freturn

Here is the same example using conversions from the smaller integral types to
long.

class Test {
public static void main(String[] args) {
long l = Byte.MAX_VALUE;
test(Short.MAX_VALUE);
l = 0L + Character.MAX_VALUE;
l = (long) Integer.MAX_VALUE;

TYPE CONVERSIONS AND THE CAST OPERATOR 603


}
static long test(long l) {
return 0;
}
}

The decompiled code for the main and test(long l) methods follows.

Method void main(java.lang.String[])


0 ldc2_w #2 <Long 127>
3 lstore_1
4 ldc2_w #4 <Long 32767>
7 invokestatic #6 <Method long test(long)>
10 pop2
11 ldc2_w #7 <Long 65535>
14 lstore_1
15 ldc2_w #9 <Long 2147483647>
18 lstore_1
19 return

Method long test(long)


0 lconst_0
1 lreturn

As you can see, the compiler effects the conversions in all cases. This is impor-
tant because programmers sometimes use numeric suffixes in an effort to avoid
the cost of a type conversion. For example, long l = 0L. Although the default
type of numeric literals is int, the numeric suffix in this assignment expression
is completely unnecessary.

5.6.2.1 Widening Primitive Conversions


Widening primitive conversions of integral types are performed by extending the
sign bit of the smaller data type to fill the high-order bits of the larger data type.

604 JAVA RULES


This is referred to as sign extension. For example, the conversion of a 16-bit
short to a 32-bit int would look like this:

Figure 5.5 Sign Extension

Widening primitive conversions from one integral type to another are unremark-
able. This is not the case, however, with widening primitive conversions from
integral to floating-point types. The potential for a loss of precision mentioned in
the last section is something that Java programmers really need to understand.
That is why I moved the discussion out of this chapter to Chapter 4, “Primitive
Data Types and Object” in Volume 1. Note also that conversions between inte-
gers and floating-point types are characterized as “nontrivial”26 in the JLS
because the two’s complement and floating-point formats are significantly differ-
ent. Such conversions should be avoided whenever possible.

5.6.2.2 Narrowing Primitive Conversions


Narrowing primitive conversions are where type conversions become interest-
ing. Based on the types involved and other common considerations such as
rounding versus truncation, I divide them into the follow six categories (the num-
ber of type conversions in each category are in brackets to help you appreciate
their relative importance).
• [1] From double to float (rounds)
• [4] From floating-point to long or int (truncates)
• [6] From floating-point to short, char, or byte (uses a special two
stage conversion)
• [9] From a wider to a narrower integral type (high-order bits are truncated)

26. Gosling et al., introduction to Chapter 5, “Conversions and Promotions.”

TYPE CONVERSIONS AND THE CAST OPERATOR 605


• [2] Primitive type conversions that are superficially neither widening nor nar-
rowing ( short to char and char to short)
• [1] Narrowing primitive conversions that are superficially widening (byte to
char only)
All of these narrowing primitive conversions are discussed in order in the remain-
der of this section.
Narrowing primitive conversions from double to float use IEEE 754
“round to nearest,” which is what most people think of as rounding. Converting
double values that are either too large or too small for the float data type
results in floating-point overflow and underflow, respectively. These subjects are
discussed in 4.6.5.2 Floating-Point Overflow and 4.6.5.3 Denormalized Numbers
and Gradual Underflow in the Second Edition of Volume 1.
When converting from floating-point to integral types, the Java programming
language uses the IEEE 754 “round to zero” mode, which stipulates that “the
result shall be the format’s value closest to and no greater in magnitude than the
infinitely precise result.”27 This is the equivalent of BigDecimal.ROUND
_DOWN . Basically it means that nothing can be added to the number being
rounded. “Round to zero” is baffling at first because it has significantly different
meanings in terms of rounding the decimal fraction versus the whole number
part of a number.
The decimal fraction is simply truncated. For example,

int i = (int) 1.999;


System.out.println(i);

The diagnostic message prints 1. To round the fraction when converting from
float to int or from double to long, use one of the following utility meth-
ods in the Math class.

public static double rint(double a)


public static int round(float a)
public static long round(double a)

27. IEEE Standard for Binary Floating-Point Arithmetic, (New York: The Institute of Electrical
and Electronics Engineers, Inc., 1985),

606 JAVA RULES


The rint(double a) method uses the same rounding mode as the floating-
point types, which is the equivalent of BigDecimal.ROUND_HALF_EVEN.
The reason why the result type is double (and not int or long) is that this
method does not round negative or positive infinity or NaN. As explained in the
API docs:
Returns the double value that is closest in value to the argument and
is equal to a mathematical integer. If two double values that are
mathematical integers are equally close, the result is the integer value
that is even. Special cases:

• If the argument value is already equal to a mathematical inte-


ger, then the result is the same as the argument.
• If the argument is NaN or an infinity or positive zero or nega-
tive zero, then the result is the same as the argument.28
The other two methods in this list use a different round mode that is the equiva-
lent of BigDecimal.ROUND_HALF_UP. This is the rounding mode taught in
elementary school. Other than rounding the fraction, these methods behave the
same as the corresponding cast operators (as described in the next paragraph).
Note that these are not overloaded methods because the result types are differ-
ent. If you use a float argument, the result type is int. If you use a double
argument, the result type is long.
Rounding the whole number in floating-point to integral type conversions is a
completely different story. High-order bits are not simply truncated. If the value
of the floating-point type is either negative infinity or less than MIN_VALUE for
the int or long data types, the value after conversion is MIN_VALUE for the
respective integral type. Likewise, if the value of the floating-point type is either
positive infinity or greater than MAX_VALUE for the int or long data types,
the value after conversion is MAX_VALUE for the respective integral type. This
applies to the cast operators as well as the round(float a) and
round(double a) methods in the Math class. For example,

class Test {
public static void main(String[] args) {

28. API docs for the rint(double a) method in the java.lang.Math class.

TYPE CONVERSIONS AND THE CAST OPERATOR 607


/*
* Careful with this first one because Double.MIN_VALUE
* is a fraction that would be simply truncated
*/
double min = -Math.pow(2,64);
double max = Double.MAX_VALUE;
double neg = Double.NEGATIVE_INFINITY;
double pos = Double.POSITIVE_INFINITY;
double NaN = Double.NaN;
double half = 1.5;
if (Math.round(min) == Long.MIN_VALUE &&
(long)min == Long.MIN_VALUE)
if (Math.round(max) == Long.MAX_VALUE &&
(long)max == Long.MAX_VALUE)
if (Math.round(neg) == Long.MIN_VALUE &&
(long)neg == Long.MIN_VALUE)
if (Math.round(pos) == Long.MAX_VALUE &&
(long)pos == Long.MAX_VALUE)
if ((long)NaN == 0 && Math.round(NaN) == 0)
System.out.println(true);
System.out.println((long) half);
System.out.println(Math.round(half));
}
}

Executing this program prints:

true
1
2

The floating-point value NaN is always converted to an integral value of zero.


As noted in Table 5.4 on page 601, floating-point values are not converted
directly to the smaller integral types short, char, and byte. Instead, the
floating-point value is first converted to an int, and then the int is converted
to a short, char, or byte. That saves having to define six machine instruc-
tions (f2s , f2c, f2b, d2s, d2c, and d2b) that would rarely be used. (It also
doubles the cost of these conversions.) The relevance of this two step conver-
sion to programmers is that direct conversions from a floating-point type that
would normally result in Short.MAX_VALUE, Character.MAX_VALUE, or
Byte.MAX_VALUE using IEEE 754 “round to zero” mode will result in a nega-

608 JAVA RULES


tive number instead. This is the result of two’s complement overflow (high-order
bits are truncated) in the second step. For example,

float f = +128;
byte b = (byte) f;
System.out.println("b = " + b);

The diagnostic message prints –128. A direct conversion from float to


byte would have resulted in +127 using IEEE standard “round to zero” mode.
The two step narrowing primitive conversion is executed internally as shown in
Figure 5.6. Extreme caution should be exercised when converting from a float-

Figure 5.6 Two Step Conversion from float to byte

ing-point to an integral type, especially for such drastic conversions as from a


floating-point type to a byte, char, or short. Note that such dangerous con-
versions are implicit when invoking the byteValue() and shortValue()
methods on a Float or Double.
As noted in Table 5.4, long to short, char, or byte are also two step
conversions. This fact, however, is not of interest to programmers because the
effect of such a two step conversion is the same truncation of high-order bits
that would occur if there were l2s, l2c and l2b machine instructions. The
rationale for these two-step conversions is the same. There are a limited number
of machine instructions in the Java programming language and these three
would rarely be used.

TYPE CONVERSIONS AND THE CAST OPERATOR 609


Narrowing primitive conversions from a wider to a narrower integral type are
performed by simply truncating the high-order bits of the larger data type. For
example, the conversion of a 32-bit int to a 16-bit short would look like this:

Figure 5.7 Converting from a wider to a narrower integral type

From this example of a narrowing primitive conversion, it can be seen that the
sign bit is always truncated. The question arises: Does the truncation of the
sign bit mean that the sign is lost? Not necessarily. If the value converted is
within the range of the smaller integral type, the sign bit is not lost. The reason
why the sign bit is not lost is the very definition of serendipity in the two’s com-
plement format. The value of a positive sign bit is zero, which is the same as the
rest of the high order bits if the number is within the range of the smaller integral
type. The same is true for negative numbers in the two’s complement format
because all of the bits of a negative number are flipped. To illustrate how this
works, Figure 5.8 compares the value of –50 in the int, short, and byte
formats:

Figure 5.8 -50 in the int, short, and byte formats

No matter which of the integral types is used to represent the value –50 , the
eight low-order bits are the same.
A char to short conversion in which the char value is +32768 or
greater results in a loss of information. That is the value of the high-order bit in

610 JAVA RULES


an unsigned char data type, which becomes a sign bit when converted to a
short. For example,

char c = +32768;
short s = (short) c;
System.out.println(s);

The diagnostic message prints -32768. Therefore, char to short conver-


sions (superficially neither widening nor narrowing) are categorized as narrowing
primitive conversions.29 The reason why short to char is categorized as nar-
rowing primitive conversion is discussed in the next paragraph.
All primitive numeric conversions to the unsigned char data type results in
a loss of the sign bit. For example,

short s = -1;
char c = (char) s;
System.out.println((int)c);

The diagnostic message prints 65535. This explains the oddity of why an 8-bit
byte to 16-bit char (superficially a widening primitive conversion) is catego-
rized as a narrowing primitive conversions. In such a conversion sign extension
would be inappropriate (because there is no sign in a char). Thus the high order
bits are always zero filled as shown in Figure 5.9. This works fine when convert-

Figure 5.9 Zero Extension in a Narrowing Primitive Conversion

ing ASCII or Latin-1 characters to a char, but is otherwise meaningless. In this


example -123 is converted to some obscure Japanese character.

29. In Java 1.1 and earlier releases, a character literal could be assigned to a byte or short type
variable without the use of a cast operator, but only for ASCII or Latin 1 characters (i.e., 8-bit val-
ues). For example, byte b = 'a' would compile. That extension of the definition of implicit narrow-
ing conversions (discussed below) was not sanctioned in the JLS and no longer compiles. See Bug
Id 4030496.

TYPE CONVERSIONS AND THE CAST OPERATOR 611


5.6.3 Reference Type Conversions
There is a subtle but important shift in the meaning of the verb to convert when
discussing primitive and reference type conversions. The machine instructions in
Table 5.4 Primitive Numeric Conversions on page 601 change the format of
the primitive numeric data. This change in format can result in a different
value. Neither the format of the data nor the object referenced in a refer-
ence type conversion changes. A reference type conversion simply means
using a different type to reference the same object. The “conversion” effectively
happens when the reference to an object is stored in a different type variable.
For example, there are five different types that can reference a PC object:
Object
Computer
Microcomputer
PC
Networkable (an interface type)
The type of the variable in which a reference to a PC object is stored would nec-
essarily have to be one of these. A reference type conversion involving a PC
object would mean assigning the reference to a different class or interface type
in this list. It is that simple. No matter how complex the rules for reference type
conversions may seem, in the end all you are doing is using a different type to
reference the same object.
Neither the value stored in a reference type variable nor the object refer-
enced is changed in any way as a result of a reference type conversion. You
need to be very clear about this. The reference value does not change because
that would mean referencing a different object on the heap. For example,

Computer comp;
PC pc = new PC();
comp = pc;
System.out.println(comp == pc);

The diagnostic message prints true proving that the value stored in the refer-
ence type variable is the same after as it was before the reference type conver-

612 JAVA RULES


sion. The PC object is also the same after as it was before the conversion. It is
easy to assume that reference type conversions somehow change the object
involved because of the “many forms” terminology of polymorphism. To “con-
vert” an object from one “form” to another implies that there is a change to the
object as shown in Figure 5.10, but that is not the case. The effect of a refer-

Figure 5.10 The class of an object never changes after it is created

ence type conversion is indeed a change in the “form” of an object, but that
change is entirely from the perspective of the programmer. The object is viewed
as having a different “form” without actually changing. In this case, a PC is
viewed as a Computer. That is to say, the object is seen through the interface
implicitly defined by public methods in the Computer class rather than
those of the PC class. The type changes, not the object. The JLS deals with
this conceptual difficulty as follows.
A cast expression converts, at run time, a value of one numeric type to
a similar value of another numeric type; or confirms , at compile time,
that the type of an expression is boolean; or checks, at run time, that a

TYPE CONVERSIONS AND THE CAST OPERATOR 613


reference value refers to an object whose class is compatible with a
specified reference type.30 [Emphasis added]

Here the verb to convert is used for primitive numeric type conversions, to con-
firm is used for identity conversions, and to check is used for reference type
conversions. This terminological problem is closely related to the different mean-
ings of data type as discussed in 4.2 The Definition of Data Types in Volume 1.
Primitive type conversions result in a change of the data format or length, which
is the older definition of data type. Reference type conversions result in a
change of the class or interface type in which a reference is stored, which is the
newer definition of type in object-oriented programming languages.
Note that some Java books refer to widening and narrowing reference type
conversions as “upcasting” and “downcasting,” respectively. The following quote
from Thinking in Java is representative.
Taking an object reference and treating it as a reference to its base
type is called upcasting, because of the way inheritance trees are
drawn with the base class at the top… to move back down the inherit-
ance hierarchy, you use a downcast.31

This terminology is not sanctioned in the JLS, however, nor is it widely used in
other Java books.

5.6.3.1 Widening Reference Conversions


There are a total of seven rules that govern widening reference type conver-
sions. Four apply to all reference types. The other three apply only to array
types. These rules define which reference types are assignment compatible.
For example,

Computer comp;
PC pc = new PC();
comp = pc;

30. Gosling et al., §15.16, “Cast Expressions.”


31. Bruce Eckel, Thinking in Java, 2nd Edition, Release 11 (Upper Saddle River: Prentice Hall,
2000), 311-312, 343.

614 JAVA RULES


In this example, the type of the class instance creation expression new PC() is
said to be assignment compatible with a Computer type variable, or more
concisely a PC is said to be assignable to a Computer. This means that the
reference conversion is classified as a widening reference conversion and is per-
mitted in all conversion contexts.
The four general rules for widening reference conversions (or assignment
compatibility) in Table 5.5 are very simple. It is important to remember that the

Table 5.5 The Four General Rules for Assignment Compatibility


Lvalue = Rvalue Rule

Object type Any reference type Any class, interface, or array type can be
converted to an Object type variable.

Any reference type null reference Any reference type can be assigned a null
reference, resulting in a null reference of that
type. If the null reference is the null literal,
no cast operator is required. If the null
reference is not the null literal and the
types involved are not assignment
compatible, a cast operator is required but
the cast operation will never throw a
ClassCastException. For example,
Object obj = null;
String s = (String)obj;

This compiles and executes without throwing


an exception.

Supertype Subtype Any subtype can be converted to any


supertype. This applies to interface types as
well as to class types.

Interface type Class type Class types can be converted to any


interface type implemented by that class. A
special case of this rule is that any array can
be converted to the Cloneable interface
type.

TYPE CONVERSIONS AND THE CAST OPERATOR 615


very same rules apply to any narrowing reference conversions that do not throw
a ClassCastException at runtime.
The difference between widening and narrowing reference conversions
involving interface types is subtle. Conversions from a class type to an inter-
face type are always widening, whereas conversions from an interface
type to a class type are always narrowing. For example, the PC class imple-
ments the Networkable interface. Consequently, the following is a widening
reference conversion:

Networkable[] node = new Networkable[dim];


PC pc = new PC();

node[i] = pc;

The same conversion in reverse is a narrowing reference conversion requiring a


cast operator:

pc = (PC) node[i];

Why is that? The answer is simply that interface types can reference any class of
objects that implements the interface. In this example, the array access expres-

Converting interface types other than to a superinterface type or


Object is always a narrowing reference conversion. It does not
matter if it is to a class that implements the interface.

sion node[i] can reference any class that implements the Networkable
interface. There is no guarantee whatsoever that such a class even will be
related to the PC class. For example, Laptop implements the Networkable
interface. That means node[i] could reference a Laptop computer. On the
other hand, node[i] could in fact reference a PC. The cast operator in effect
tells the compiler that you are sure the node[i] references a PC. If node[i]
references any other class of object at runtime, a ClassCastException is
thrown. For example,

class Test {
public static void main(String[] args) {

616 JAVA RULES


Printable p = new Printable() {
public void print() {
System.out.println("Anonymous Class");
}
};
Subclass subclass = (Subclass) p;
}
}
class Superclass { }
class Subclass extends Superclass implements Printable {
public void print() {
System.out.println("Subclass");
}
}
interface Printable {
void print();
}

The bold line of code is a narrowing reference conversion because p could refer-
ence any class that implements the Printable interface. In this case, p refer-
ences an anonymous class. The compiler generated name of the anonymous
class is Test$1 , which like all anonymous classes is a direct subclass of
Object. This program compiles successfully. Attempting to execute the pro-
gram, however, throws a ClassCastException.
The remainder of this section discusses the three rules for array type
assignment compatibility. Before discussing these rules, however, you must
be able to distinguish between an array type and the component type of that
array. An array type is the component type followed by one or more pairs of
empty brackets indicating the number of dimensions. For example,
float[] gpa = new float[class.size()]

This is a one dimensional array of float. The array type is float[]. The
component type is float. The length of the array is not part of the array type.
That much is obvious in this example because we have no idea what the value of
class.size() is going to be at runtime. Array and component types are dis-
cussed further in 6.2 Array, Component, and Element Types in Volume 1.
The first rule applies to all array type conversions. To be assignment com-
patible, array types must have the same number of dimensions. There is no

TYPE CONVERSIONS AND THE CAST OPERATOR 617


exception to this rule. When initializing a multidimensional array, the dimensional
nodes and leaf arrays may appear to have a different number of dimensions. For
example,

a[0] = new int[2][]; //dimensional node

This is deceptive, however, because there is no way of telling the type of the
unnamed variable in the first element of a[] by just looking at the array access
expression a[0]. If it is not int[][], this line of code will not compile.
The other two rules in Table 5.6 are for the component types. The last rule

Table 5.6 Array Type Assignment Compatibility Rules


Lvalue = Rvalue Rule

Primitive type Primitive type Both component types must be the same primitive
type.

Reference typea Reference type The rvalue component type must be assignable to
the lvalue component type (according to the rules
in Table 5.5)
a. This is the component type used in the declaration of the array variable, not the component type of the array
referenced. They could be different. The component type of the array referenced is pertinent to the
ArrayStoreException discussion in 5.7.1.1 The ArrayStoreException.

explains why array type assignment compatibility could not be discussed in


Chapter 6, “Arrays and The Collections Framework” in Volume 1. You cannot
understand array type assignment compatibility without first understanding the
four general rules for all widening reference conversions.
As an example of the first of these two array type assignment compatibility
rules, the following code does not compile although the same primitive type con-
version (int to double) is classified as a widening primitive conversion.

int[] a1 = new int[10];


double[] a2;
a2 = (double[]) a1; //COMPILER ERROR

So an int can be assigned to a long, but an int[] cannot be assigned to a


long[] .

618 JAVA RULES


It should be fully understood that widening reference conversions are the
only reference type conversions. Narrowing reference conversions are refer-
ence type conversions that either turn out to be widening reference conversions
at runtime or throw a ClassCastException. Conceptually, this is one of the
most important points to grasp about reference type conversions. The subject
of narrowing reference conversions is discussed at length in the following sub-
section.

5.6.3.2 Narrowing Reference Conversions


Narrowing reference conversions are not a second set of rules for reference type
conversions. They involve types that are by definition not assignment compatible.
There exists, however, the possibility that the class of the object referenced at

Narrowing reference conversions are all about possibilities.

runtime will be assignable to the type of the variable or other expression in a nar-
rowing reference conversion. If that is not the case, a ClassCastException
is thrown.
Narrowing reference conversions involving class types are always from a
superclass to a subclass. For example,

PC pc = (PC) comp;

The declared type of the comp variable is Computer. The conversion is catego-
rized as a narrowing reference conversion because Computer is a superclass
of PC. If comp actually references a Computer class object at run time, a
ClassCastException would be thrown because references to Computer
objects are not assignable to PC type variables.
So why does a reference type conversion such as pc = (PC) comp com-
pile at all? The answer is widening reference conversions. Because of widening
reference conversions such as comp = pc, the compiler has no way of deter-
mining the class of object referenced at run time. It could be any class in the
Computer class hierarchy or an interface implemented by any of those

TYPE CONVERSIONS AND THE CAST OPERATOR 619


classes. In other words, given the class hierarchy in Figure 5.2 on page 592,
comp could reference any of the following:
Computer
Supercomputer
Mainframe
Microcomputer
PDA
PC
NC
Laptop
Networkable (an interface)
For pc = (PC) comp, the only assignment compatible types in this list are PC
and Networkable. A narrowing reference conversion allows for the possibil-
ity that the class of the object referenced at run time will be one of those assign-
ment compatible types. If that is not the case, a ClassCastException is
thrown.
The remainder of this section discusses narrowing reference type conver-
sions involving interface types. There is only one forbidden conversion involving
interface types. All other conversions between interface types are catego-
rized as either widening or narrowing reference conversions. If you are
new to type conversions, this should come as a surprise because it means that
with a little help from a cast operator the compiler permits almost any conver-
sion involving interface types. For example,

class Test {
static java.rmi.Remote remote;
static java.util.RandomAccess random;
static Cloneable cloneable;
public static void main(String[] args) {
remote = (java.rmi.Remote)random;
cloneable = (Cloneable)remote;
random = (java.util.RandomAccess)cloneable;
}
}

620 JAVA RULES


These are all tagging interfaces (no members) that have nothing in common. The
ability to convert practically any interface type to another interface type merely
by adding a cast operator is very different from type conversions involving class
types. Class types must be related in order to be classified as either widening or
narrowing reference conversions.
Why such a huge difference? The answer is that the compiler must allow for
the possibility that a class hierarchy will be extended by a subclass that imple-
ments the interface named in a reference type conversion. For example,

class Test {
static Superclass s = new Superclass();
static Printable p;
public static void main(String[] args) {
p = (Printable) Test.s;
}
}
class Superclass { } //there will be a subclass added momentarily
interface Printable {
void print();
}

Although Superclass does not implement the Printable interface, this


program compiles. Attempting to execute it, however, throws a ClassCast-
Exception. So why does the program compile when it cannot be executed?
The Test program compiles because the Superclass class hierarchy could
be extended by a subclass that implements Printable. For example,
class Test {
static Superclass s = new Subclass();
static Printable p;
public static void main(String[] args) {
p = (Printable) Test.s;
}
}
class Superclass { }
class Subclass extends Superclass implements Printable {
public void print() {
System.out.println("Subclass");
}
}

TYPE CONVERSIONS AND THE CAST OPERATOR 621


interface Printable {
void print();
}

Note that Test.s is now initialized with an instance of Subclass, which is


why this program compiles and executes without throwing a ClassCast-
Exception .
What happens if the assumption that the reference type could be extended
by a subclass that implements the interface is removed? This is exactly what
happens if the class is declared final. The only interface types that can be
converted to a final class are those actually implemented by that class. For
example,

class Test {
static Superclass s = new Superclass();
static Printable p;
public static void main(String[] args) {
p = (Printable) Test.s;
}
}
final class Superclass { }
interface Printable {
void print();
}

This is exactly the same as the original program except for the final keyword
in the Superclass declaration. Attempting to compile the program now gen-
erates the following compiler errors:

Test.java:5: inconvertible types


found : Superclass
required: Printable
p = (Printable) Test.s;
^
1 error

The compiler knows a lot more about the class hierarchy, and can do more pre-
cise compile-time type checking. This is one reason for routinely capping class
hierarchies as discussed in 3.10 Capping a Class Hierarchy.

622 JAVA RULES


5.7 Conversion Contexts
There are only five conversion contexts in the JLS. This can only be described as
an oversight because there are clearly at least seven conversion contexts in the
Java programming language, eight if you view any use of a compound assign-
ment operator as a separate conversion context. In Java Rules, the assign-
ment conversion context is separated into the “simple assignment
conversion” and “compound assignment conversion” contexts. Furthermore, any
use of a compound assignment operator such as += is described as an explicit
conversion. The rationale for this decision is explained in 5.7.5 The Implicit Cast
in a Compound Assignment Operation. The (other) missing conversion contexts
in the JLS are the method return and exception handling conversion contexts.
Table 5.7 is a complete list of conversion contexts. It is important to under-

Table 5.7 Conversion Contexts


Conversion Context Choice of Type Conversion

Simple Assignment No

Method Invocation Yes

Method Return No

String No

Numeric Promotion Yes

Exception Handling Yes

Compound Assignment No

Casting No

stand that any use of a cast operator is a casting conversion. It does not matter
if the cast operator appears in an assignment or return statement, before an
argument in a method invocation or class instance creation expression, in a
string concatenation operation, or in an operator expression, any use of a cast
operator is the casting conversion context. Three of the conversion contexts
in Table 5.7 are highly specialized and not discussed in this chapter:

TYPE CONVERSIONS AND THE CAST OPERATOR 623


• String conversion
• Numeric promotion
• Exception handling conversion
The rules for type conversions are complicated enough without including these
highly specialized conversion contexts. My objection to including them in a more
general discussion of type conversions is as follows.
• All data types can be converted to the String data type using the string
concatenation operators + and +=. Such conversions cross over all of what
are described as type conversion boundaries in this book. This
requires constantly making exceptions to the rules for the more
general type conversions. Doing so amounts to little more than an unnec-
essary distraction and significantly muddies the water of type conversion
rules. String conversions are discussed in 5.11.2 Implicit String Conver-
sions in Volume 1.
• Numeric promotions are best discussed in the context of operations. To
quote the JLS:
Numeric promotion is not a general feature of Java, but rather
a property of the specific definitions of the built-in opera-
tions.32

Shift operations are a prime example of this. Although the operands of a


shift operator are primitive numeric types, binary numeric promotion is not
performed. Instead, the operands are promoted using unary numeric pro-
motion. Numeric promotion is discussed in 4.3.1 Numeric Promotion.
• The (class) type of an exception object is automatically converted to the
exception handler type of a matching catch clause (which may be nothing
more than an identity conversion). For example, a FileNotFound-
Exception is automatically converted to an IOException. I refer to
this as the exception handling conversion context. It is not discussed
until 6.8.1 The catch Clause.
Each of these are listed as one of the conversion contexts for the sake of com-
pleteness, but that is the only mention of them in this chapter.

32. Gosling et al., §5.6, “Numeric Promotions.”

624 JAVA RULES


Conversion contexts are significant because they determine which categories
of type conversions are allowed. An important example of this are the “implicit nar-
rowing conversions” defined in 5.7.1 Simple Assignment Conversion Context. They

The design of the Java programming language is that safe type con-
versions are automatic in all conversion contexts.

are allowed in the assignment conversion context but not in method invocation or
return. Table 5.8 summarizes which type conversions are allowed in a given con-
text. There are two important points about type conversions that this table makes

Table 5.8 Allowable Conversions in a Given Context


Identity and Implicit Narrowing Narrowing
Conversion Widening Narrowing Primitive Reference
Context Conversions Conversions Conversions Conversions

Method Invocation ✔
Method Return ✔
Exception Handling ✔
Numeric Promotion ✔
String ✔
Simple Assignment ✔ ✔
Compound
Assignment
✔ ✔ ✔
Casting n/a (always
✔ explicit)
✔ ✔

very clear:

TYPE CONVERSIONS AND THE CAST OPERATOR 625


• Safe type conversions are automatic in all conversion contexts
• The only context in which any type conversion is allowed is the use of a cast
operator
I put a check mark under “implicit narrowing conversions” for the compound
assignment conversion context for the sake of simplicity. Unlike the simple
assignment conversion context, however, a cast operator is used to implement
int to byte, int to short, and int to char conversions when using a
compound assignment operator even if the int value is a compile-time constant
expression in the range of the smaller integral type. For example,

class Test {
public static void main(String[] args) {
byte b = 0;
b += 0;
}
}

Here is the decompiled code for the main method showing that a cast operator
must be used to implement the compound assignment operator even though the
equivalent operation using a simple assignment operator merely stores the value
of the compile-time constant:

Method void main(java.lang.String[])


0 iconst_0
1 istore_1
2 iload_1
3 iconst_0
4 iadd
5 i2b
6 istore_1
7 return

As with other uses of a compound assignment operator, I do not regard such


type conversions as implicit.
In most conversion contexts the type of expression after the type conversion
is immediately obvious. For example, in either of the two assignment conversion
contexts, the type of the expression is always the declared type of the variable
denoted by the left-hand operand. Likewise, in the method return conversion con-

626 JAVA RULES


text, the type of an expression is the result type of the method invoked; in the
casting conversion context, the type of the expression is the type name in the
cast operator; and in the string conversion context (any use of the + and +=
string concatenation operators), the type is always String. The other three
conversion contexts (method invocation, exception handling, and numeric pro-
motion) are different in this regard. As stated in the JLS:
The term “conversion” is also used to describe the process of choos-
ing a specific conversion for such a context. For example, we say
that an expression that is an actual argument in a method invocation is
subject to “method invocation conversion,” meaning that a specific con-
version will be implicitly chosen for that expression according to the
rules for the method invocation argument context.33 [emphasis added]

Method invocation is only the most obvious of these three; the compiler must
also choose between one of the four computational types when promoting the
operands in an operator expression; and a JVM must either choose an exception
handler parameter type from one of the dynamically enclosing catch clause or
else invoke the default exception handler.

5.7.1 Simple Assignment Conversion Context


Any use of the simple assignment operator (including variable declarations) is a
simple assignment conversion context. The type of the right-hand operand
must be converted to the declared type of the variable denoted by the left-hand
operand before the assignment operation can be performed. For example,

class Test {
public static void main(String[] args) {
int i = 0;
long l = i;
}
}

The decompiled code for the main method follows.

33. Gosling et al., introduction to Chapter 5, “Conversions and Promotions.”

TYPE CONVERSIONS AND THE CAST OPERATOR 627


Method void main(java.lang.String[])
0 iconst_0
1 istore_1
2 iload_1
3 i2l
4 lstore_2
5 return

The i2l machine instruction in this decompiled code implements the implicit
int to long conversion. If the operands are reference types, they must be
assignment compatible as defined in 5.6.3.1 Widening Reference Conversions
above.
The simple assignment conversion context is unlike any other (including the
compound assignment conversion context) because of what are referred to as
implicit narrowing conversions. The term implicit narrowing conversions
should have the same puzzling effect as does any oxymoron. Implicit conver-
sions by definition are safe, and narrowing conversions by definition are unsafe.
Therefore, without any further explanation, the term “implicit narrowing conver-
sions” should sound like “safe unsafe conversions.”
Safe conversions include only widening primitive conversions, widening ref-
erence conversions, identity conversions, and string conversions with one impor-
tant exception. The designers of the Java programming language have defined a
subset of narrowing primitive conversions that are always safe. Therefore, they
are implicit, but only in the simple assignment conversion context.
Why define a subset of “implicit” narrowing conversions? Quite simply to make
it possible to initialize a byte, short, or (less frequently) char variables with
an integer literal without having to use an explicit cast. Remember that the
default type of an integer literal is int. Without implicit narrowing conversions,
variable declarations as simple as byte b = 0 would require the use of a cast
operator.
Such so-called “implicit narrowing conversions” are not really conversions at
all. They are merely compiler checks to make sure the value assigned is within
the range of the byte, short, or char type variable. For example,

628 JAVA RULES


byte b = -128;
short s = 32767;
char c = 65535;

The default type of the numeric literals in these assignment expressions is int.
Normally they would require conversion to the byte, short, and char data
types, but as you can see in the following decompiled code the i2b, i2s, and
i2c machine instructions are not used.

0 bipush -128
2 istore_1
3 sipush 32767
6 istore_2
7 ldc #2 <Integer 65535>
9 istore_3
10 return

The value of the compile-time constant expression is assigned directly to the


byte, short, and char type variables.
The specification for implicit narrowing conversions reads as follows.
• The [right-hand operand] expression is a [compile-time] constant expression
of type byte, short, char, or int.
• The type of the variable [denoted by the left-hand operand expression] is
byte, short, or char.
• The value of the [right-hand operand] expression (which is known at compile
time, because it is a constant expression) is representable in the type of the
variable.34
None of the following conversions compile because they do not satisfy all of the
criteria for implicit narrowing conversions.

int i = '\uffff'; //initialize an int variable with a char value

byte b = -129; //COMPILER ERROR (out of range)


short s = 32767 + 1; //COMPILER ERROR (out of range)
char c = i; //COMPILER ERROR (variable)

34. Gosling et al., The Java Language Specification, §5.2, “Assignment Conversion.”

TYPE CONVERSIONS AND THE CAST OPERATOR 629


The value –129 is not representable in a byte. And 32767 + 1 is not repre-
sentable in a short. The variable initializer for c is a variable, which by defini-
tion is not a constant expression.
Implicit narrowing conversions do not work in the method invocation conver-
sion context. If a method or constructor parameter is a byte, char, or
short , the corresponding argument expression cannot be an integer literal
(unless a cast operator is used). For example,

class Test {
public static void main(String[] args) {
doSomething(0);
}
static void doSomething(byte b) { }
}

Attempting to compile this program generates the following compiler error:

Test.java:3: doSomething(byte) in Test cannot be applied to (int)


doSomething(0);
^
1 error

It simply does not matter if the argument is a constant expression that “is repre-
sentable in the type of the” method or constructor parameter.
There is a lengthy explanation for this difference in the JLS. It is quoted here
in its entirety:
Method invocation conversions specifically do not include the implicit
narrowing of integer constants which is part of assignment conversion.
The Java designers felt that including these implicit narrowing conver-
sions would add additional complexity to the overloaded method
matching resolution process. Thus, the example:

class Test {
static int m(byte a, int b) { return a+b; }
static int m(short a, short b) { return a-b; }
public static void main(String[] args) {
System.out.println(m(12, 2)); //compile-time error
}
}

630 JAVA RULES


causes a compile-time error because the integer literals 12 and 2 have
type int, so neither method m matches under the rules [for over-
loaded method matching]. A language that included implicit narrowing
of integer constants would need additional rules to resolve cases like
this example.35

The “overloaded method matching resolution process” is discussed in 5.8 Over-


loaded Method Matching.

5.7.1.1 The ArrayStoreException


If the compiler does not require the use of a cast operator in the right-hand oper-
and of an assignment statement, the assignment statement will never throw a
ClassCastException. The fact that the compiler does not require a cast
operator means that the type conversion is completely safe. Or does it?
There is an automatic run-time “type check” of all assignments to the
unnamed variables in an array, but only if the component type is a reference
type. If the class of the object referenced is not assignment compatible with the
component type of the array, an ArrayStoreException is thrown. For
example,

class Test {
public static void main(String[] args) {
Object[] array = new String[10];
array[0] = new Object(); //throws ArrayStoreException
}
}

This program compiles because the component type of the array variable is
Object. The component type changes to String at run time, however, when
a String[] type array is assigned to the same array variable. It is precisely
such narrowing reference type conversions between array types that lead to the
ArrayStoreException being thrown at run time. Assignments to primitive
type arrays never throw an ArrayStoreException because the element
type and the component type are necessarily always the same.
The relevant section of the JLS includes the following statement.

35. Gosling et al., §5.3, “Method Invocation Conversion.”

TYPE CONVERSIONS AND THE CAST OPERATOR 631


This check is similar to a narrowing cast, except that if the check fails,
an ArrayStoreException is thrown rather than a
ClassCastException.36

I find this to be very misleading. An ArrayStoreException is not the array


equivalent of a ClassCastException. For one thing, there is no cast opera-
tor involved. The run-time “type check” is implicit. Furthermore, it vaguely sug-
gests that assignments to array type variables do not throw a ClassCast-
Exception . The name of the exception is ArrayStoreException. An
ArrayStoreException means that the class type of an object referenced
at run time cannot be assigned to one of the unnamed variables in an array.
Array type assignment compatibility is an entirely different discussion. A narrow-
ing conversion involving array types still throws a ClassCastException at
run time if the arrays are not assignment compatible as defined in 5.6.3.1 Wid-
ening Reference Conversions.

5.7.2 Method Invocation Conversion Context


Except for no-argument methods and constructors, any method invocation or
class instance creation expression is a method invocation conversion con-
text. The type of an argument expression must be converted to the correspond-
ing formal parameter type before the method (including the <init> methods
that correspond to constructors) can be invoked. For example,

class Test {
public static void main(String[] args) {
float f = 0;
test(f);
}
static void test(double d) { }
}

Here is the decompiled code for the main method:

Method void main(java.lang.String[])


0 fconst_0
1 fstore_1

36. Gosling et al., §15.26.1, “Simple Assignment Operator =.”

632 JAVA RULES


2 fload_1
3 f2d
4 invokestatic #2 <Method void test(double)>
7 return

The f2d machine instruction in this decompiled code implements the implicit
float-to-double conversion. If the operands are reference types, they must be
assignment compatible as defined in 5.6.3.1 Widening Reference Conversions.
The only difference between the simple assignment and method invocation
conversion contexts is that the “implicit narrowing conversions” defined in 5.7.1
Simple Assignment Conversion Context are not allowed. For example,

class Test {
public static void main(String[] args) {
test(0);
}
static void test(byte b) { }
}

Attempting to compile this program generates the following compiler error:

Test.java:3: test(byte) in Test cannot be applied to (int)


test(0);
^
1 error

The javac compiler is doing you a favor by pointing this out. Other, less
sophisticated Java compilers might act as if the test(byte b) method does
not even exist.
The “cannot be applied” terminology in this error message seems a little odd
at first. Elsewhere in the JLS this process is described as “choosing” a type con-
version (which in this case means choosing a method). The following quote from
5.7 Conversion Contexts is repeated here for your convenience.
The term “conversion” is also used to describe the process of choos-
ing a specific conversion for such a context. For example, we say
that an expression that is an actual argument in a method invocation is
subject to “method invocation conversion,” meaning that a specific con-
version will be implicitly chosen for that expression according to the
rules for the method invocation argument context.37 [emphasis added]

TYPE CONVERSIONS AND THE CAST OPERATOR 633


“Implicitly choosing” a type conversion for the arguments in a method invocation
expression is the same as “applying” the method invocation expression to a par-
ticular method declaration in the qualifying type.
The term applicable method is a very important one, especially when try-
ing to understand the overloaded method matching algorithm discussed in 5.8
Overloaded Method Matching. It is defined in the JLS as follows.
A method declaration is applicable to a method invocation if and only
if both of the following are true:

• The number of parameters in the method declaration equals


the number of argument expressions in the method invoca-
tion.
• The type of each actual argument can be converted by
method invocation conversion to the type of the correspond-
ing parameter.38
The “by method invocation conversion” in the last bulleted item simply means
that the type conversion must be safe (or more precisely either an identity or
widening conversion as shown in Table 5.8 Allowable Conversions in a Given
Context on page 625). The test(byte b) method in Test “cannot be
applied to (int)” because int to byte is a narrowing (read unsafe) type
conversion, which is not allowed in the method invocation conversion context.
Note that the javac compiler also refers to applicable methods as
matching methods. For example,

class Test extends Superclass {


public static void main(String[] args) {
byte b = 0;
new Test().test(b);
}
void test(int i) { }
}
class Superclass {
void test(byte b) { }
}

37. Gosling et al., The Java Language Specification, introduction to Chapter 5, “Conversions and
Promotions.”
38. Gosling et al., §15.12.2, “Compile-Time Step 2: Determine Method Signature.”

634 JAVA RULES


Under the old overloaded method matching algorithm in pre 1.4.2 releases,
attempting to compile this program would generate the following compiler error:

Test.java:4: reference to test is ambiguous, both method


test(byte) in Superclass and method test(int) in Test match
new Test().test(b);
^
1 error

The test(byte b) method is now referred to as a “matching” method instead


of “applicable.” The rationale for this distinction between “applicable” and
“matching” methods in javac error messages is actually quite subtle; the types
of argument expressions in a method invocation or class instance creation
expression are not considered part of overloaded method matching; only the
signatures of the applicable methods are. Thus “applicable” methods
become “matching” after the compiler has applied the overloaded
method matching algorithm.39 See 5.8 Overloaded Method Matching for a
complete discussion.
The implicit (or automatic) widening conversions in the method invocation
conversion context are what I like to describe as built-in method overloading.
For example, consider the Math.pow(double a, double b) method. The
double parameter types can be passed any combination of integer or floating-
point literals. Thus one method does the work of many. This is something that
you should at least take into consideration when declaring a method or construc-
tor that has primitive numeric type parameters. Another thing to consider is the
default data types for numeric literals (int and double). In the case of pass-
ing floating-point values, both built-in method overloading and default data types
favour the use of double as a parameter type. If a float type parameter is
used, either a cast operator or numeric suffix will be required to invoke
the method or constructor using a floating-point literal. This is a slight
inconvenience for client programmers, but does not mean that all floating-point
parameters should be declared double any more than all reference type

39. Neal Gafter confirmed this when he said the following in a personal email: “...the word ‘match’ in
... compiler diagnostic[s]... means ‘are accessible and applicable’. We use ‘match’ because it's
shorter.”

TYPE CONVERSIONS AND THE CAST OPERATOR 635


parameters should be declared Object. Nothing comes more readily to mind
in this context than the HashMap(int initialCapacity, float
loadFactor) constructor because load factors are almost always passed as
numeric literals. Why did Bloch choose float instead of double, thus forcing
client programmers to use a numeric suffix every time this constructor is
invoked? The answer is simply that double is absurdly large for a load factor.
The method invocation conversion context is discussed further in 5.8 Over-
loaded Method Matching.

5.7.3 Method Return Conversion Context


Although the JLS does not describe return statements as a conversion con-
text, the value of the expression in a return statement is nevertheless implic-
itly converted to the result type. The following “example of a widening
conversion that loses precision”40 is from the JLS:

class Test {
public static void main(String[] args) {
int big = 1234567890;
float approx = big;
System.out.println(big - (int)approx);
}
}

Executing this program prints -46. The loss of precision occurred because the
value 1234567890 was implicitly converted to the float data type in the
assignment statement float approx = big. Now consider the following
modification to that program.

class Test {
static int big = 1234567890;
public static void main(String[] args) {
System.out.println(big - (int)approx());
}
public static float approx() {
return big;

40. Gosling at al., §5.1.2, “Widening Primitive Conversions.”

636 JAVA RULES


}
}

Instead of assigning big to a float type variable, big is returned by a float


type method. The result is the same. Executing the modified program still prints
-46. Although the type of the expression in the return statement is int and
the method invocation expression is explicitly cast to int, the value
1234567890 is still (implicitly) converted to the float data type resulting in
the same loss of precision as the assignment expression in the JLS example.
Here is the decompiled code for the approx() method:

Method float approx()


0 getstatic #3 <Field int big>
3 i2f
4 freturn

This shows conclusively that return statements are a conversion context. The
name for such a conversion context is rather naturally the method return con-
version context. Even the language used to describe the return statement is
similar to that used to describe the assignment conversion context. Compare
the following two quotes:
The type [of the return expression] must be assignable to the
declared result type of the method, or a compile-time error occurs.41

Assignment conversion occurs when the value of an expression is


assigned to a variable: the type of the expression must be converted to
the type of the variable.42

The absence of a method return conversion context in the JLS is clearly an error
of omission. As with other conversion contexts, if the result type in a method
declaration is a reference type, the expression in a return statement must
evaluate to an assignment compatible type.

41. Gosling at al., §14.16, “The return Statement.”


42. Ibid., §5.2, “Assignment Conversion.”

TYPE CONVERSIONS AND THE CAST OPERATOR 637


5.7.4 The Cast Operator
A cast operator is required for all narrowing primitive and reference type conver-
sions (except for the implicit narrowing conversions of primitive numeric types in
the assignment conversion context). It is really nothing more than the compiler
forcing a programmer to acknowledge that the requested conversion is unsafe
(and therefore may result in a loss of information or throw a ClassCast-
Exception ). The machine instructions that implement cast operators either
convert primitive data types or else they check the class of an object at runtime.
For narrowing primitive type conversions, the machine instructions are listed in
Table 5.4 on page 601. There is nothing much that can be added to the fact that
they convert a numeric value from one primitive numeric type to another. This
section primarily discusses the cast operator as used in narrowing reference
conversions.
All narrowing reference conversions use the same opcode 192,
checkcast machine instruction, which does nothing more than to “check” that
the object referenced at runtime is an instance of the type named in the “cast”
operator. Hence, the mnemonic “checkcast.” The reference type value at the
top of the operand stack is the same after as before checkcast is executed.
In fact, the only thing checkcast does besides checking that the object refer-
enced is an instance of the type name in the cast operator is to throw a
ClassCastException if the types are not assignment compatible. Rather
than actually converting anything, the cast operator in a narrowing reference
conversion merely guarantees that the class of the object referenced at runtime
is assignment compatible with the type of the variable in which the reference is
stored.
Few programmers are aware that the detailed descriptions of opcode 192
checkcast and opcode 193 instanceof in the JVMS are nearly identical.
In fact, the specification for both of those machine instructions include a note
such as the following.
The instanceof instruction is very similar to the checkcast instruction.
It differs in its treatment of null, its behavior when its test fails
(checkcast throws an exception, instanceof pushes a result code),
and its effect on the operand stack.43

638 JAVA RULES


The instanceof operator asks a question about the class of the object ref-
erenced, whereas the cast operator requires that the class of the object refer-
enced is an instance of the type name in the cast operator. It is that simple.
Except for null references, the instanceof operator will return false

The exact same run-time “type checks” are used to implement both
the checkcast and instanceof machine instructions.

under exactly the same conditions that a cast operator will throw a Class-
CastException. Likewise, the instanceof operator will not compile
under exactly the same conditions as a cast operator. Therefore, you should
have no more qualms about using a cast operator than you do using the
instanceof operator.
The main difference between checkcast and instanceof is null ref-
erences. As stated in Table 5.5 The Four General Rules for Assignment Compat-
ibility on page 615, null can be assigned to any reference type. If the left-hand
operand of the instanceof type comparison operator is a null reference,
however, false is always returned. For example,

class Test {
public static void main(String[] args) {
Object objectref = null;
if (objectref instanceof String) //instanceof
System.out.println("objectref instanceof String");
String s = (String)objectref; //checkcast
}
}

This program compiles, but prints nothing. The only other difference is that a
cast operator throws a ClassCastException instead of returning false.
For example,

class Test {
public static void main(String[] args) {

43. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, Notes for the
instanceof machine instruction in Chapter 6, “The Java Virtual Machine Instruction Set.”

TYPE CONVERSIONS AND THE CAST OPERATOR 639


Object objectref = new Object();
if (objectref instanceof String) //instanceof
System.out.println("objectref instanceof String");
String s = (String)objectref; //checkcast
}
}

This program still does not print anything, but it now throws a ClassCast-
Exception (because Object is not assignable to String).
The run-time “type checks” for both the instanceof and checkcast
machine instructions are stated as follows.
If S is the class of the object referred to by the objectref and T is the
resolved class, array, or interface type, checkcast determines
whether objectref can be cast to type T [or instanceof deter-
mines whether objectref is an instance of T] as follows:

• If S is an ordinary (nonarray) class, then:


♦ If T is a class type, then S must be the same class as T
or a subclass of T.
♦ If T is an interface type, then S must implement interface
T.
• If S is an interface type, then:
♦ If T is a class type, then T must be Object.
♦ If T is an interface type, then T must be the same inter-
face as S, or a superinterface of S.
• If S is a class representing the array type SC[], that is, an
array of components of type SC, then:
♦ If T is a class type, then T must be Object.
♦ If T is an array type TC[], that is, an array of components
of type TC, then one of the following must be true:
° TC and SC are the same primitive type.
° TC and SC are reference types, and type SC can be
cast to TC by recursive application of these rules.
• If T is an interface type, T must be one of the interfaces
implemented by arrays. 44
The point is quoting these specifications is to show you that they are identical
(except for the bracketed differences). Do not try to memorize these rules,
however. They are exactly the same rules for assignment compatibility as are

640 JAVA RULES


discussed in the relevant sections of this chapter. In other words, the specifica-
tion for these machine instructions are a JVMS presentation of the rules for wid-
ening primitive conversions as discussed in 5.6.2.1 Widening Primitive
Conversions and 5.6.3.1 Widening Reference Conversions.
For every assignment expression involving a narrowing reference conversion
there are three type checks. Two are compile-time type checks:
• The type name in the cast operator is checked against the declared type of
the variable denoted by the left-hand operand
• The type of the cast expression (to the right of the cast operator) is
checked against the type name in the cast operator
In a sense, these two compile-time type checks are completely unrelated. The
latter intended to keep the type conversion from compiling if it is sure to throw a
ClassCastException at runtime. For example,

Mainframe mainframe = new Mainframe();


PC pc = new PC();
mainframe = (Mainframe) pc; //COMPILER ERROR

Attempting to compile this code generates the following compiler error:

Test.java:5: inconvertible types


found : PC
required: Mainframe
mainframe = (Mainframe) pc; //COMPILER ERROR
^
1 error

The Mainframe and PC classes are unrelated. Therefore such a conversion is


forbidden. The other compile-time type check is part of how Java “guarantees”
that the class of an object referenced at run time is always compatible with the
type of the variable in which it is stored. This works because at run time
Opcode 192, checkcast, checks to make sure that the class type of the
object referenced is assignment compatible with the type name in the cast oper-

44. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, Second Edition,
§6.4, “Format of Instruction Descriptions” on pages 193-194 (checkcast ) and 278-279
(instanceof). Reading these specifications from the JVMS, you can see why run-time class (or
interface) check is the more appropriate term

TYPE CONVERSIONS AND THE CAST OPERATOR 641


ator. In other words, Java uses the transitive property of algebra to “guarantee”
the class of an object referenced in a narrowing reference conversion will be
assignment compatible with the type of the variable that references it. This tran-
sitive property is explained in Table 5.9.

Table 5.9 The Transitive Property in Narrowing Reference Conversions


Transitive
Property Type Checking

a = b Compile-time type checking checks that the type name in a cast operator is
assignment compatible with the variable or expression that references an
object. For example,

pc = (PC) node[i];

Compile-time type checking checks that the PC type name in the cast
operator is assignment compatible with the declared type of the pc variable.
(In this case, they are the same.)

b = c Opcode 192, checkcast checks to make sure that the class of the
object referenced at runtime is assignment compatible with the type named in
a cast operator. Hence the name checkcast . In the assignment
statement pc = (PC) node[i] , the checkcast machine instruction
would check that the class of the object referenced by node[i] is
assignment compatible with the PC type. Note that a ClassCast-
Exception always means that the class of the object referenced at
runtime is not assignment compatible with the type name in the cast operator
(as opposed to the type of the variable denoted by the left-hand operand of an
assignment expression). There is an example below in which that fact becomes
relevant.

a = c Therefore, the class of the object referenced at runtime must be assignment


compatible with the type of the variable or expression that references it. This
is how Java “guarantees” that the class of the object referenced by
node[i] at run time in the assignment expression pc = (PC)
node[i] is assignment compatible with the declared type of the pc
variable even though this is a narrowing reference conversion.

642 JAVA RULES


5.7.5 The Implicit Cast in a Compound Assignment Operation
Note that the JLS does not consider the use of a compound assignment opera-
tor an explicit conversion. In fact, Chapter 5, “Conversions and Promotions” of
the JLS inexplicably fails to even mention compound assignment operators.
However, there is a reference to an “implied cast” in the following quote:
A compound assignment expression of the form E1 op = E2 is equiva-
lent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that
E1 is evaluated only once. Note that the implied cast to type T may
be either an identity conversion or a narrowing primitive conversion.45
[emphasis added]

This book describes the use of a compound assignment operator as an explicit


conversion for two reasons.
The first reason is partly a reaction to the fact that the implicit cast in a com-
pound assignment operator is more often than not completely overlooked in
Java books. Even when it is mentioned, very little attention is given to the fact
that the cast operation can result in an unintentional loss of information. Com-
pound assignment operators are almost universally described as “short cuts,”
implying that a compound assignment operator such as += is the same as using
the product of an additive expression in a simple assignment operation, but this
is not so. A simple assignment operation uses only safe conversions, whereas
the “implied cast” in a compound assignment operation may result in the uninten-
tional loss of information. For example,

int i = Math.PI; //COMPILER ERROR

This simple assignment expression does not compile. The type of the right-hand
operand expression Math.PI is double. A conversion from double to int
obviously can result in a loss of information. Now consider the same expression
written using a compound assignment operator:

int i = 0;
i += Math.PI;
System.out.println(i);

45. Gosling at al., §15.26.2, “Compound Assignment Operators.”

TYPE CONVERSIONS AND THE CAST OPERATOR 643


The diagnostic message prints 3. There is an obvious loss of information. Now
compare that compound assignment expression with the following explicit cast.

int i = (int) (Math.PI);

The value of i is the same as after the compound assignment operation. In both
cases, there is a loss of information. The implied cast means that considerably
more care must be taken when using compound assignment operators.
The second reason for describing the use of a compound assignment opera-
tor as an explicit conversion is that, unlike simple assignment conversion, there
is an alternative means for doing the same thing. The operations do not have to
be compounded. They can be written separately. The choice is the program-
mer’s. All I am saying is that the effect of using a compound assignment opera-
tor is the same as using a cast operator if the implicit cast is unsafe. That much
is obvious. If a programmer knows this, then the use of a compound assignment
operator is in fact an explicit conversion.

5.8 Overloaded Method Matching


This section discusses the rules that a Java compiler uses when there is more
than one applicable method as defined on page 634. The compiler must choose
the most specific (applicable) method. The implication is that the method is over-
loaded because “applicable” methods necessarily have the same name. Hence
the rules discussed in this section are referred to as overloaded method
matching in the JLS.46 The same rules apply to constructors, however, which
means the most specific method may actually be the most specific construc-
tor.
The 1.4.2 FCS release 47 will introduce a very significant change to over-
loaded method matching; the “declaring class” of applicable methods will no
longer be used to select the most specific applicable method. Consequently
there are two subsections. 5.8.1 Choosing The Most Specific Applicable Method

46. Gosling et al., §5.3, “Method Invocation Conversion.”


47. Neal Gafter (a senior software engineer at Sun and member of the Java Spec Report) has said
that the changes will not be implemented until the 1.4.2 FCS release. See groups.yahoo.com/
group/java-spec-report/message/849. They definitely were not implemented in the 1.4.2
Beta release.

644 JAVA RULES


discusses the rules for overloaded method matching in the 1.4.2 and future
releases. 5.8.2 The Declaring Class of Applicable Methods provides some back-
ground on the old overloaded method matching algorithm in pre 1.4.2 releases
and explains why the declaring class of applicable methods is no longer relevant.
Readers new to the Java programming language really should not read that sub-
section. It is intended for experienced Java programmers trying to fully under-
stand changes to overloaded method matching.

5.8.1 Choosing The Most Specific Applicable Method


Overloaded method matching is the process of choosing the most specific
applicable method. I use the term most specific applicable method instead
of the JLS most specific method very deliberately. The key to understanding
overloaded method matching is that it begins with a list of applicable method sig-
natures. The type(s) of the actual argument expression(s) in a method invocation
or class instance creation expression used to select the applicable methods in
that list simply are not used in overloaded method matching. I cannot over-
emphasize this point. When there is more than one applicable method, over-

The type(s) of actual argument expression(s) are not used in over-


loaded method matching.

loaded method matching only uses the types of their formal parameters to either
choose the most specific one or else generate a compiler error saying that the
method invocation or class instance creation expression is ambiguous.
The rule for choosing the most specific applicable method is very simple.
Each parameter in the applicable method signature must be narrower
than the same parameter in all of the other applicable methods. That is
precisely what makes it the most specific applicable method. For example, con-
sider the overloaded println methods in the PrintStream and Print-
Writer classes. Given a byte argument, all four of the overloaded println
methods in Figure 5.11 are applicable. This is just another way of saying that a
byte type argument can be converted to an int , long, float, or double
(all of which are widening conversions) in the method invocation conversion con-

TYPE CONVERSIONS AND THE CAST OPERATOR 645


Figure 5.11 More than One Applicable Method

text. The println(int) method is chosen because int is a narrower primi-


tive type than long, float, or double. It is that simple (most of the time).
It is important to understand that in choosing the most specific method from
a list of applicable methods there is no possibility of a narrowing conver-
sion from an actual argument type to the corresponding formal parameter
type because the definition of applicable methods as given on page 634
Here is a much less obvious example that will test your understanding of
overloaded method matching:

import java.io.Serializable;
class Test {
public static void main(String[] args) {
new Test().test(new Integer(0));
}
void test(Number n) {
System.out.println("test(Number n)");
}
void test(Serializable s) {
System.out.println("test(Serializable s)");
}
}

Which of these applicable methods is the most specific? If you are thinking in
terms of the type of the argument expression new Interger(0) then it
would be quite natural to expect this program to print test(Number n)
because Integer extends Number. However, Integer also implements
Serializable, which means that both methods are applicable. The program
actually prints test(Serializable s) because Serializable is nar-
rower than Number.

646 JAVA RULES


As stated above, if the applicable methods have more than one parameter,
each parameter in the most specific applicable method must be narrower than
the same parameter in all of the other applicable methods. The following exam-
ple is from the JLS.

class Point { int x, y; }


class ColoredPoint extends Point { int color; }

class Test {
static void test(ColoredPoint p, Point q) {
System.out.println("(ColoredPoint, Point)");
}
static void test(Point p, ColoredPoint q) {
System.out.println("(Point, ColoredPoint)");
}
public static void main(String[] args) {
ColoredPoint cp = new ColoredPoint();
test(cp, cp); // compile-time error
}
}48

Attempting to compile this program generates the following compiler error:

Test.java:13: reference to test is ambiguous, both method


test(ColoredPoint,Point) in Test
and method test(Point,ColoredPoint) in Test match
test(cp, cp); // compile-time error
^
1 error

If there is no one applicable method in which all of the parameters are the nar-
rowest, then the method invocation or class instance creation expression is
ambiguous. In test(ColoredPoint p, Point q) only the first parameter
is narrower. Likewise, in test(Point p, ColoredPoint q) only the sec-
ond parameter is narrower. The method invocation test(ColoredPoint,
ColoredPoint) in main is therefore ambiguous.
Using null as an argument expression is a special case that 15.12.2.2
Choose the Most Specific Method of the JLS does not directly address. I believe

48. Gosling et al., §15.12.2.3, “Example: Overloading Ambiguity.”

TYPE CONVERSIONS AND THE CAST OPERATOR 647


it should because the resulting behavior is not always intuitive. The Evaluation of
Bug Id 4366660 was most helpful in this regard.
When multiple method definitions are applicable, Java chooses the
“most specific” one, and only reports an ambibugity [sic] if no such
method exists. See JLS 2e 15.12.2.2.

I admit that this behavior may be confusing due to the fact that
the literal null belongs to all reference types (i.e., the null type is
convertible to any reference type). In the more common case,
where the argument expression belongs to a named class or interface
type, choosing the most specific method generally results in the intu-
itively expected behavior.49 [emphasis added]

Consider the following example.

class Test {
public Test(Object o) {
System.err.println("null is an Object");
}
public Test(String s) {
System.err.println("null is a String");
}
public static void main(String[] args) {
new Test(null);
}
}

Executing this program prints null is a String because String is nar-


rower than Object. In fact, in examples of overloaded method matching
such as this in which there is only one formal parameter, the applicable
method with an Object type parameter is never chosen.
As far as I know, I believe that as of the 1.4.2 release it is generally not pos-
sible for single parameter methods to be ambiguous. It is definitely not possible
when the parameter type of the applicable methods is primitive. In the two
exceptions to this rule of which I am aware, the parameter types of the applica-
ble methods are reference types:

49. Evaluation of Bug Id 4366660.

648 JAVA RULES


• Using null as an argument expression
• Multiple inheritance in interface hierarchies
What both of these examples have in common is that the formal parameter types
of the applicable methods may be unrelated class or interface types .
When null is used as an argument, method invocation or class instance
creation expressions only compile if the parameter types of all the applicable
methods (or constructors) are related. If an unrelated type is thrown into the mix,
an ambiguous method error is always generated. For example,

class Test {
public Test(Object o) {
System.err.println("null is an Object");
}
public Test(String s) {
System.err.println("null is a String");
}
public Test(StringBuffer sb) {
System.err.println("null is a StringBuffer");
}
public static void main(String[] args) {
new Test(null);
}
}

Attempting to compile this program generates the following compiler error:

Test.java:12: reference to Test is ambiguous, both method


Test(java.lang.String) in Test and method
Test(java.lang.StringBuffer) in Test match
new Test(null);
^
1 error

It is impossible for String to be narrower than StringBuffer or vice versa


because these are unrelated types.
In an interface type hierarchy involving multiple inheritance, it is also possible
for the applicable methods to be unrelated. For example,

class Test {
public static void main(String[] args) {
C c = new C() {

TYPE CONVERSIONS AND THE CAST OPERATOR 649


public void f(A a) { }
public void f(B b) { }
};
c.f(c);
}
}
interface A {}
interface B {}
interface C extends A, B {
void f(A a);
void f(B b);
}

Attempting to compile this program generates essentially the same error mes-
sage because interfaces A and B are unrelated; neither extends that other.
Note that autoboxing, unboxing, and varargs in the 1.5 “Tiger” release will
further complicate overloaded method matching.50 I will not address this issue
until both the beta version of the compiler and a draft copy of the Third Edition of
the JLS is released.

NOTE 5.2
The following section introduces a new term; the class in which a
method is declared is referred to as the declaring class by at least
one software engineer at Sun. (See the Evaluation of Bug Ids 4814557
and 4761586.) This is significantly different than the qualifying type.
The reason for pointing this out is that the two are sometimes confused
when discussing overloaded method matching.

5.8.2 The Declaring Class of Applicable Methods


Prior to the 1.4.2 release, the overloaded method matching algorithm was much
more complicated. Not only did each of the method parameters in the applicable
method have to be the narrowest, but so did the declaring class (which has been

50. This sentence is a paraphrase of one in an email that Neal Gafter sent me. The skeleton of the
previous example was also provided by Gafter in the same email.

650 JAVA RULES


referred to as the most specific declaring class by at least one software engi-
neer at Sun).51 For example,

class Test extends Superclass {


public static void main(String[] args) {
new Test().print(0);
}
}
class Baseclass {
public void print(int i) {
System.out.println(i);
}
}
class Superclass extends Baseclass {
public void print(long l) {
System.out.println(l);
}
}

Attempting to compile this program in a pre 1.4.2 release generates the follow-
ing compiler error:

Test.java:3: reference to print is ambiguous, both method


print(int) in Baseclass and method print(long) in Superclass
match
new Test().print(0);
^
1 error

As of the 1.4.2 FCS release this code should compile. The question that every-
one would like answered is why did it not compile in previous releases? Here is
the same example with the method signatures in the Baseclass and Super-
class reversed:

class Test extends Superclass {


public static void main(String[] args) {
new Test().print(0);
}
}
class Baseclass {

51. See the Evaluation of Bug Id 4814557.

TYPE CONVERSIONS AND THE CAST OPERATOR 651


public void print(long l) {
System.out.println(l);
}
}
class Superclass extends Baseclass {
public void print(int i) {
System.out.println(i);
}
}

This example compiles in all releases. Why the difference? This is very confusing
for application programmers because in both cases print(int i) and
print(long l) are members of the Baseclass; so why are they treated
differently based on their declaring class? The remainder of this section
attempts to answer that question and then explains why the language designers
have reversed this decision.
There has never been an official explanation why the class in which an appli-
cable method is declared was ever part of the overloaded method matching
algorithm. In fact, the Evaluation of Bug Id 4814557 includes the following sen-
tence.
There isn't a very good reason why the specification is the way it is with
respect to this issue. 52

Not even Neal Gafter knows the precise reason why, and he is the software engi-
neer responsible for the development of the javac compiler.53
The apparently unforeseen problem with the overloaded method matching
algorithm used in pre 1.4.2 releases is that it requires client programmers know
the declaring class of overloaded methods. This “exposes the structure of the
type hierarchy to clients”54 In other words, it breaks encapsulation (in the worst
kind of way). For example,

52. Ibid.
53. Gafter has said only that “I suspect the authors of the first edition JLS were defining the VM and
language at the same time and simply got their concepts confused.” This was in a personal email to
the author. Believe me when I say that few if any other technical writers or software engineers have
tried as diligently as I to get to the bottom of this. THERE IS NO BOTTOM. Apparently no one knows
for sure except perhaps the original authors of the JLS and they are not saying.
54. See the Evaluation of Bug Ids 4814557 and 4761586.

652 JAVA RULES


class Superclass {
void print(char c) {
System.out.println(c);
}
}
class Subclass extends Superclass {
void print(int i) {
System.out.println(i);
}
}
class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
sub.print('A');
}
}

Attempting to compile this program generates the following compiler error:

Test.java:14: reference to print is ambiguous, both method


print(char) in Superclass and method print(int) in Subclass match
sub.print('A');
^
1 error

The problem is that the most specific applicable method is in Superclass,


which is not allowed under the old rules.
There are two ways to fix this problem. If the client programmer is aware of
the problem of using the declaring class in the overloaded method matching
algorithm (which excludes a great many Java programmers), then he or she can
assign the reference to the superclass type in which the overloaded method is
declared or else use a cast. For example,

class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
((Superclass)sub).print('A');
Superclass temp = sub;
temp.print('A');
}
}

TYPE CONVERSIONS AND THE CAST OPERATOR 653


The Test program now compiles. This baffles application programmers, how-
ever, because it appears as if print(char c) is not in fact a member of
Subclass. The more common fix is to add a print(char c) method to

Using the declaring class of applicable methods in overloaded


method matching breaks encapsulation.

Subclass and then to use message forwarding to invoke the same method in
Superclass . For example,

class Superclass {
void print(char c) {
System.out.println(c);
}
}
class Subclass extends Superclass {
void print(int i) {
System.out.println(i);
}
void print(char c) {
super.print(c);
}

}
class Test {
public static void main(String[] args) {
Subclass sub = new Subclass();
sub.print('A');
}
}

This amounts to an ugly workaround for a problem that should not exist. From a
API design standpoint, it is highly undesirable because there is no apparent rea-
son for redeclaring the print(char c) method in the subclass. Even more
troubling, this is the only exception to the rule that inherited members are
exactly the same as members declared in a class or interface. There should be
no exceptions to that rule.
That this not a theoretical problem can be seen in Bug Ids 4109924 and
4114065. Here is the description from the latter:

654 JAVA RULES


Graphics2D declares drawString(String, float,
float). This overloads Graphics drawString(String,
int, int). But because of the method invocation rules, trying to call
drawString(String, int, int) on an instance of
Graphics2D is declared ambiguous by the compiler.
The intent of this method is to add the capability to specify position
with float precision to the drawString method, not to require that
clients always explicitly use float precision when using
Graphics2D. Since the other overloads of drawString in
Graphics2D are not ambiguous and will promote int to float
gracefully, this particular overload stands out as unusually difficult to
use.55

In fact prior to the 1.3 release, the drawString(String, float,


float) in Graphics2D would have to be called as follows.

((Graphics)g2).drawString((String) vector.get(i), 1,
(yy += strheight));

This is a modified example from Bug Id 4109924. The problem was solved by
redeclaring drawString(String, int, int) in the Graphics2D sub-
class in the 1.3 release.
As stated above, the use of the declaring class in overloaded method match-
ing will be dropped as of the 1.4.2 FCS release. This is not a cosmetic change to
the specification; programs that did not compile prior to the introduction of the
change will now compile. In other words, the language itself has changed, not
just the specification. It is a backwards compatible change because it only
affects code that previously would not compile. A complex mathematical proof
attributed to Gilad Bracha that this change is backwards compatible can be found
at groups.yahoo.com/group/java-spec-report/message/849.
Finally, note that the declaring class of applicable methods was never used
when choosing the most specific class method. For example,

class Test extends Superclass {


public static void main(String[] args) {

55. See the Description of Bug Id 4114065.

TYPE CONVERSIONS AND THE CAST OPERATOR 655


Test.print(0);
}
}
class Baseclass {
public static void print(int i) {
System.out.println(i);
}
}
class Superclass extends Baseclass {
public static void print(long l) {
System.out.println(l);
}
}

This is the same example that generated an ambiguous method error at the
beginning of this section, only the static modifier has been added to the
method declarations. This program, however, compiles. That is a problem only
in that the relevant specification makes no distinction whatsoever between
instance and class methods. The 1.4.2 FCS release will fix this problem because
the declaring class of applicable methods will no longer be part of the over-
loaded method matching algorithm. In other words, this “bug” will become nor-
mal behavior (as it should be for class methods).

5.9 Value Set Conversions


Value set conversions are all about different floating-point modes. Each floating-
point mode has a different “value set.” For the default floating-point mode, the
float and double value sets is used. For the FP-strict floating-point mode,
the float- and double-extended-exponent value sets are used. The section on
value set conversion in the JVMS is much more informative than the correspond-
ing section of the JLS. The term value set conversion is defined in the JVMS as
the “process of mapping a floating-point value from one value set to another
without changing its type.”56 The “without changing its type” is important
because, as the name implies, value set conversions are not type conversions.
They are changes in the format and length of binary data within the same data
type. These are one-way conversions from either the float-extended-exponent

56. Tim Lindholm and Frank Yellin, §5.1.8, “Value Set Conversion.”

656 JAVA RULES


value set to the float value set or from the double-extended-exponent value
set to the double value set. Such value set conversions are required, for
example, when invoking a method that is not FP-strict from within a method that
is FP-strict, but only if “the implementation has chosen to represent the result of
the method invocation as an element of an extended-exponent value set.”57
None of this is of interest to mainstream business application programmers,
however, for the same reason that they can safely ignore the existence of the
strictfp modifier. The only significance of value set conversion is that it
may result in floating-point overflow or underflow, each of which involves
numbers so large or so small that they simply cannot be found in main-
stream business applications. See 4.3.2.2 Floating-Point Modes in Volume 1
for a detailed discussion.

57. Gosling at al., §3.8.3, “Value Set Conversion.”

TYPE CONVERSIONS AND THE CAST OPERATOR 657


658 JAVA RULES
Chapter 6

Assertions, Exceptions, and Logging

Chapter Contents
6.1 Introduction 660
6.2 Assertions 661
6.2.1 Preconditions, Postconditions, and Invariants 671
6.2.2 assert false and Logic Traps (or Control-flow Invariants) 679
6.2.3 Catching CloneNotSupportedException 682
6.3 An Execution Stack Primer 685
6.4 The Exception Mechanism 690
6.5 The Throwable Class Hierarchy 698
6.5.1 General Exception Classes 723
6.5.1.1 Umbrella Exceptions 729
6.5.2 Unspecified Runtime Exceptions and Errors 736
6.5.2.1 The Chicken Little “The Sky is Falling” Problem 754
6.5.3 Asynchronous Exceptions 759
6.6 Throwable Objects 762
6.6.1 The Loggable Interface 767
6.7 The throw Statement 768
6.8 The try Statement 785
6.8.1 The catch Clause 791
6.8.1.1 Catchall Exception Handlers 799
6.8.2 The finally Clause 817
6.8.2.1 try-finally as a Control-flow Statement 818
6.8.2.2 Releasing System Resources 823
6.9 Exception Handling 859
6.9.1 Rethrowing the Same Exception 864
6.9.2 Exception Translation and Chaining 867
6.9.3 Squelching an Exception 874
6.9.4 Setting an Error Flag 878
6.9.5 Retry 879
6.9.6 Try an Alternative 881

ASSERTIONS, EXCEPTIONS, AND LOGGING 659


6.10 Uncaught Exceptions 883
6.10.1 Top-level Exception Handlers 884
6.10.1.1 The AWTExceptionHandler Class 908
6.10.1.2 The UncaughtException Class 917
6.10.2 A Stack Trace Primer 922
6.11 Logging 936
6.11.1 The Logger Namespace 952
6.11.2 Logging Configuration 957
6.11.3 Logging Methods 975
6.11.4 The Cleaner Thread (A Shutdown Hook in LogManager) 984

6.1 Introduction
If the Boolean expression in an assertion evaluates to false, an Assertion-
Error is thrown, which explains why assertions and exceptions are discussed
in the same chapter. Likewise, there is a very close relationship between excep-
tion handling and logging.
There are three sections in this chapter that are inherently controversial
because they fly into the face of conventional wisdom:
• 6.5 The Throwable Class Hierarchy in which I argue that there has been a
paradigm shift in the fundamental definition of errors. This section is the
linchpin of the entire chapter
• 6.5.2.1 The Chicken Little “The Sky is Falling” Problem in which I argue that
errors should not be used to shut down an application program (except dur-
ing system initialization)
• 6.8.1.1 Catchall Exception Handlers in which I argue that catch(Throw-
able e) should be the catchall exception handler of choice
I have no desire to become the pied piper of exception handling, so I encourage
you to read these sections with a very critical eye. If I am wrong, then I am
wrong on a massive scale. This is particularly true of my definition of errors
which infuses the entire chapter.
This chapter uses SystemConfigurationError so much that an
unsuspecting reader might think it is a standard error class declared in the
java.lang package. It is not. SystemConfigurationError is an
Error subclass that I propose should be added to the core API. It should be

660 JAVA RULES


thrown whenever a class file, resource bundle, system property, character
encoding, DLL file, or the like considered to be part of either a Java product
such as the J2SE or the host operating system has gone missing.
A minimal knowledge of stacks is required in order to understand some of
the concepts discussed in this chapter. 6.3 An Execution Stack Primer is a very
elementary introduction to the concept of stacks and frames. It used to be a
part of 6.4 The Exception Mechanism, but the explanation of stacks and frames
grew so that I had to create a separate section.
In the electronic edition of Java Rules that sold for years prior to the publi-
cation of this book, this chapter discussed a simple logging implementation that
consisted of several classes and a Loggable interface. That implementation
has since been replaced by the java.util.logging package, but the dis-
cussion of the Loggable interface remains. I still think it is a good idea, one
that could be implemented in the core API. The problem that the Loggable
interface solves can be seen in the following quote from Effective Java.
If the failure is not easily reproducible, it may be difficult or impossible
to get any more information. Therefore it is critically important that the
exception’s toString method return as much information about the
cause of the failure as possible. In other words, the string representa-
tion of an exception should capture the failure for subsequent analy-
sis.1

The Loggable interface is an alternative to cramming everything into a detail


message. Doing so is problematic on systems that display the detail message to
end users (and therefore may need to internationalize the message). An excep-
tion class that implements Loggable can write directly to a log file.

6.2 Assertions
If the boolean type expression that immediately follows the assert keyword
evaluates to false, an AssertionError is thrown. In other words, by using
the assert keyword, you are asserting that something is true. As stated in

1. Joshua Bloch, Effective Java Programming Language Guide, (Boston: Addison-Wesley,


2001), “Item 45: Include failure-capture information in detail messages.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 661


the introduction to this chapter, AssertionError is a very special kind of
error because the responsible programmer or development team is in complete
control of the code that would result in such an error being thrown. This explains
why it is safe to disable assertions in production. Code that has been fully devel-
oped and tested should never thrown an AssertionError.
The question that must be answered is: What does “in complete control”
mean? This question is best answered by example. The classic example is argument
checks versus assertions. In a public or protected method, argument checks
are used in part because a client or subclass programmer is in control. In a
private method, however, the responsible programmer is in complete control,
and in a package-private method either the responsible programmer or a develop-
ment team is in complete control. For example,

*
* @param newCapacity the new capacity, MUST be a power of two.
*/
void resize(int newCapacity) {
assert (newCapacity & -newCapacity) == newCapacity; //power of 2

This is an actual assertion (though commented out) from a package-private


method in the HashMap class. In this case the precondition “MUST be a power
of two” is tested using an assertion rather than an argument check. (This use of
bitwise operators is discussed in 4.7.5 Some Miscellaneous Uses of the Bitwise
Operators.) The package-private resize(int newCapacity) method is
only invoked in HashMap and the LinkedHashMap subclass. Here is a typi-
cal invocation from the putAll(Map t) bulk method in the HashMap class:

int capacity = table.length;


while (capacity < n)
capacity <<= 1;
resize(capacity);

The capacity is initially set to the size of the old hash table. The local variable
n has been set to the size of the new table (not shown here). Because it is
already known that n has exceeded the threshold of the old hash table,
capacity is sure to be less than n the first time the while loop is executed.

662 JAVA RULES


How is all this relevant to assertions? My point in explaining the details of this
method invocation is so that you understand that capacity is always going to
be a power of two. So why bother testing the capacity argument inside the
resize(int newCapacity) method?
If you have never used assertions before, then I really hope you are asking
yourself the same question. This is what makes assertions so peculiar at first.
You are testing assumptions about your own code. Doing so may seem
completely unnecessary, but an experienced programmer knows the value of
always testing an assumption, any assumption. In fact, testing the assumption
that your code works is what assertions are all about. As stated in the “Over-
view” section of the “Programming With Assertions” document:
By verifying that the boolean expression is indeed true, the system cor-
roborates the programmer's knowledge of the program thus increasing
the possibility that the program is free of errors.2

Because the responsible programmer is in complete control, there should be no


need to continually check the assumption after the class or package is fully
developed and tested. What can change?
This brings us to the difficult question of trusting other programmers to
properly implement their interface contracts. Within the same package, yes. You
can trust the implementation of other classes in the same package, even to the
point of including their code in an assertion. Outside of the package is an entirely
different matter. At one level we are constantly trusting that other programmers
properly implement their contracts. As Bloch said in a Bill Venner interview:
I think you have no choice but to trust that objects implement their con-
tracts. Once people start violating their contracts, the whole world falls
apart. A simple example: Equal objects must have equal hash codes. If
you created types for which this isn't true, hash tables and hash sets
wouldn't work.

More generally, when objects stop obeying their contracts, objects


around them start to break -- that's just the way it is. I can understand

2. Unascribed, “Programming With Assertions” in the API docs for the 1.4 release, (Mountain View:
Sun Microsystems, 2002), Introduction.

ASSERTIONS, EXCEPTIONS, AND LOGGING 663


why it makes you nervous, but, yes, you do have to trust objects to
implement their contracts. If you feel nervous, you can take a hint from
the intelligence community and “trust but verify.” The best way to do
this is with assertions, because you don't pay for them when you turn
them off. Use assertions to test that other objects are obeying their
contracts, and, if your program starts acting strangely, enable asser-
tions and you may well find out who is to blame.3

This level of trust (or distrust) is entirely different than including code from
another package in an assertion. You should never assume that someone
else’s code works in an assertion. Doing so implies that you are not “in com-
plete control.” There is a very closely related discussion of asserting that an
exception is not thrown near the top of 6.9.2 Exception Translation and Chaining
on page 867. In that discussion, I make a few minor exceptions to the rule that
you should not trust code from another package (at least as far as assertions
are concerned).
The general format of an assertion is as follows.

assert Expression1 : Expression2;


The first expression must evaluate to the boolean data type. It is a required
part of the syntax. The second expression is optional. When used, it is separated
from the first expression by a colon. The second expression can evaluate to
any data type, but void methods cannot be invoked. In other words, the
second expression is not a top-level expression. If the assertion fails (i.e., if the
first expression evaluates to false), the value of the second expression is
passed to one of the fully overloaded AssertionError constructors, which
then converts the argument to a string. In short, the second expression is com-
parable to the detail message 4 passed to other constructors in the

3. Joshua Bloch in an interview with Bill Venner entitled “A Conversation with Josh Bloch” (First Pub-
lished in JavaWorld, January 4, 2002), on the artima.com Web site (Artima Software, Inc.),
www.artima.com/intv/blochP.html.
4. The API docs for the Throwable class refer to detailed error messages as just detail mes-
sages . I suspect their reason for doing so is that calling this an “error” message might confuse
some programmers because of the Error class. The API docs for the Throwable class even
describe the “detail message” as providing “more information about the error.” I do not like the term
detail message but have grow accustomed to it. It is therefore consistently used throughout this
chapter.

664 JAVA RULES


Throwable class hierarchy. Fully overloading the AssertionError con-
structors is what makes it possible to specify that the second expression can
evaluate to any data type (but not void). This design is intended primarily to
make it easy to use int type error codes (referred to as “unique message IDs”5
in the “Java Logging Overview” document) instead of string messages. Omitting
the optional second expression is comparable to invoking the no-argument
Assertion-Error() constructor. Not many programmers use the optional
second expression, so most assertions consist of the assert keyword fol-
lowed by a boolean type expression and then a semicolon.
Assertions can be either simple or complex. A complex assertion is one in
which a method is invoked. Of course, the result type of such a method would
necessarily be boolean. There is a very important example of a complex
assertion that was added to the Thread class in the 1.4 release. The holds-
Lock(Object obj) method is documented as follows.
Returns true if and only if the current thread holds the monitor lock
on the specified object.

This method is designed to allow a program to assert that the current


thread already holds a specified lock:

assert Thread.holdsLock(obj);6

A simple assertion is any boolean type expression other than a method invo-
cation expression.
There is a brief period during class loading in which assertions are always
enabled. This brief period is while superclasses are being initialized and before
the <clinit> method is executed. For example,

class Test extends Superclass {


public static void main(String[] args) {
assertionStatus();
}

5. Unascribed, “Java Logging Overview” in the API docs for the 1.4 release, (Mountain View: Sun
Microsystems, 2002), §1.13, “Unique Message IDs.”
6. API docs for the holdsLock(Object obj) method in the java.lang.Thread class.
This complex assertion is being used extensively in the 1.4 release in non-public methods that
should only be called after obtaining some lock.

ASSERTIONS, EXCEPTIONS, AND LOGGING 665


static void assertionStatus() {
boolean assertionsEnabled = false;
assert assertionsEnabled = true; //intentional side effect
if (assertionsEnabled)
System.out.println("assertions are enabled");
else
System.out.println("assertions are disabled");
}
}
class Superclass {
static {
Test.assertionStatus();
System.out.println(Test.class.desiredAssertionStatus());
}
}

Executing this program prints:

assertions are enabled


false
assertions are disabled

That assertions are always on during superclass initialization is a rather obscure


point. I only mention it because it helps to explain the rather oddly named
desiredAssertionStatus() method in the Class class. The API docs
state that “few programmers will have any need for this method; it is provided
for the benefit of the JRE itself. (It allows a class to determine at the time that it
is initialized whether assertions should be enabled.).”7 Nevertheless, without
some explanation, the name of this method is perplexing.
Other than this brief window during class initialization, assertions are dis-
abled by default. They can be enabled using the -ea command-line option,
which is the abbreviated form of the -enableassertions option. If -ea is
used by itself, assertions are enabled for all non-system code. A separate -esa
(or -enablesystemassertions) option is used to enable assertions in the
core API. To enable assertions in the unnamed package only use an ellipsis after
the -ea option. For example,

C:\Java\classes>java -ea … Test

7. API docs for the desiredAssertionStatus() method in the Class class

666 JAVA RULES


To enable assertions in one or more named packages, use the fully qualified
package name after the -ea option. For example,

C:\Java\classes>java -ea com.javarules.examples Test

If an ellipsis is used after the package name, assertions are also enabled in all of
the subpackages. For example,

C:\Java\classes>java -ea com.javarules.examples … Test

To enable assertions in one or more classes, use the fully qualified class name
after the -ea option. For example,

C:\Java\classes>java -ea com.javarules.examples.Widget Test

There are also a -da (or -disableassertions) option. Because asser-


tions are disabled by default, the only use of this option is to selectively dis-
able assertions for classes within a package. For example,
C:\Java\classes>java -ea com.javarules.examples -da
com.javarules.examples.Widget Test

Command-line options such as these are processed in the order in which they
are written, so this combination of options enables assertions for all of the
classes in the com.javarules.examples package except for the
Widget class.
Assertions are primarily used during development to debug programs. They
are usually disabled in production. The design of all diagnostic tools is to mini-
mize their cost in a fully developed application that has been deployed:
• With simple diagnostic messages printed to the console using standard I/O,
this usually means removing them as soon as the problem is found. If left in
a program, they are usually removed by the compiler by means of condi-
tional compilation
• With logging, this means not logging trace information (Level.FINE,
Level.FINER, and Level.FINEST)
• With assertions, this means disabling them

ASSERTIONS, EXCEPTIONS, AND LOGGING 667


There is still overhead associated with disabled assertions, however. Otherwise
the core API programmers would not be commenting them out. The com-
piler adds the following declaration to every class that uses assertions.

static final boolean $assertionsDisabled;

This blank final is initialized by the <clinit> method when the class is loaded
(based either on the -ea, -esa, and -da command-line options or the default).
The first thing that is done when executing an assert statement is to check
the value of $assertionsDisabled. If it is false, then the Boolean
expression in the assertion is not evaluated. For example, the compiler trans-
forms assert a > b into the following.

if ($assertionsDisabled)
if (!(a > b))
throw new AssertionError();

Thus the minimum cost of an assertion is if ($assertionsDisabled),


even if assertions are disabled. I suspect in the case of the core API, assertions
are being commented out because of the hidden additional cost of class loading
(however miniscule). The fact that there is a cost incurred even when assertions
are disabled explains why the “Enabling and Disabling Assertions” part of the
assertions Design FAQ begins with the following question.
Why not provide a compiler flag to completely eliminate asser-
tions from object files?

It is a firm requirement that it be possible to enable assertions in the


field, for enhanced serviceability. It would have been possible to also
permit developers to eliminate assertions from object files at compile
time. However, since assertions can contain side effects (though they
should not), such a flag could alter the behavior of a program in signifi-
cant ways. It is viewed as good thing that there is only one semantics
associated with each valid Java program. Also, we want to encourage
users to leave asserts in object files so they can be enabled in the field.
Finally, the standard Java “conditional compilation idiom” described in
JLS 14.19 can be used to achieve this effect for developers who really
want it.8

668 JAVA RULES


Elsewhere in the same document it says that assertions are used for “debugging
in the field,”9 but these comments must be put in context.
A field service engineer initial reads the log report. Assertions are not likely
to be enabled unless determining the cause of a problem proves elusive, but it is
unlikely that they will provide any useful information.

What makes assertions invaluable to field service engineers is that in


the unlikely event that an AssertionError is thrown in the field,
the problem would have been otherwise very difficult to diagnose.

After all, what we are talking about here are Boolean expressions that even the
responsible programmer assumes to be true. This is precisely why Sun wants
“to encourage users to leave asserts in object files so they can be enabled in the
field.”8 For field service engineers, assertions are like a highly specialized tool.
They may not use them very often, but when needed there is no substitute for
enabling assertions.
It is precisely because assertions are typically disabled in production code
that you must be extremely careful not to execute any code that has a side
effect. As stated in the “Assertion Facilities” document:
Because assertions may be disabled, programs must not assume that
the boolean expressions contained in assertions will be evaluated. Thus
these expressions should be free of side effects. That is, evaluating
such an expression should not affect any state that is visible after the
evaluation is complete. Although it is not illegal for a boolean expres-
sion contained in an assertion to have a side effect, it is generally inap-
propriate, as it could cause program behavior to vary depending on
whether assertions are enabled or disabled.9

For example, consider the following assertion.

boolean removed = list.remove(null);


assert removed;

8. Unascribed, “Programming with Assertions” in the API docs for the 1.4 release, Design FAQ -
Enabling and Disabling Assertions.
9. Ibid., Design FAQ - General Questions.

ASSERTIONS, EXCEPTIONS, AND LOGGING 669


The first line of code is supposed to be executed in production. That makes the
following variation of the assertion a gross error because the null element(s)
will not be removed in production once assertions are disabled.

assert list.remove(null);

Normally side effects are thought of as what a method does besides returning a
value, but in this case merely assigning a value to a field is considered a side
effect.
If assertions must be enabled, the API docs include the following suggestion:
Requiring that Assertions are Enabled

Programmers of certain critical systems might wish to ensure that


assertions are not disabled in the field. Here is an idiom that prevents a
class from being loaded if assertions have been disabled for that class:

static {
boolean assertsEnabled = false;
assert assertsEnabled = true; // Intentional side effect!!!
if (!assertsEnabled)
throw new RuntimeException("Asserts must be enabled!!!");
}

I would only add that the fact that assertions are disabled when they are sup-
posed to be enabled is neither a bug nor a recoverable exception. Such a class
should throw an AssertionsDisabledError and document the fact using
an @throws tag. This is a classic example of a recurring end-user error as
discussed in 6.5 The Throwable Class Hierarchy below.
Sun implementations such as the J2SE are in a “transitional period” during
which the -source 1.4 compiler option must be used in order to compile
using assertions. The reason for this is simply to ease the transition from
assert being a legal identifier to it now being a keyword. All sorts of compiler
errors are generated if you attempt to use assertions without this option. This
raises an interesting question of why there is both a -source and -target
option. They are documented as follows.

670 JAVA RULES


-source release

Enables support for compiling source code containing asser-


tions.

When release is set to 1.4, the compiler accepts code con-


taining assertions. Assertions were introduced in J2SE 1.4.

When release is set to 1.3, the compiler does not support


assertions. The compiler defaults to the 1.3 behavior if the -
source flag is not used

-target version

Generate class files that will work on VMs with the specified
version. The default is to generate class files to be compatible
with the 1.2 VM in the Java 2 SDK.…10

My first inclination was that it would make it possible to generate a class file that
used assertions but could be run on a 1.2 VM. That is not the case, however. For
example,

C:\Java\classes>javac -source 1.4 -target 1.2 Test.java

Attempting to compile Test.java with this combination of options generates


the following compile error:

javac: source release 1.4 requires target release 1.4

Likewise, compiling a class that uses assertions using only the -target 1.4
does not work because assert is not treated as a keyword. The only answer
then is that there is both a -source and -target option is so that older
class files (in which assert is not a keyword) can be targeted to a 1.4 VM.

6.2.1 Preconditions, Postconditions, and Invariants


Assertions are usually discussed in terms of preconditions, postconditions,
and invariants. This is the language of Design by Contract (also known by the
initialism DBC), which is a trademark of Interactive Software Engineering,

10. Unascribed, “Java 2 SDK Tools and Utilities” in the API docs for the 1.4.1 release, (Mountain
View: Sun Microsystems, 2002), “javac - Java programming language compiler.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 671


designers of the Eiffel programming language. Assertions are only used to test
preconditions in private and default access methods. Arguments checks
must used in public and protected methods. (The difference being that
assertions can be disabled whereas argument checks cannot.) Preconditions are
discussed in 1.6.3.1 Argument Checks. This section primarily focuses on invari-
ants and postconditions. Because invariants and postconditions are always sup-
posed to be true (including public and protected methods) they are very
naturally tested using assertions.
While precondition and invariant have been part of the English language
since 1825 and 1874, respectively, according to Merriam-Webster's Collegiate
Dictionary,11 the same dictionary does not even define postcondition. This usage
history is important to point out because the term invariant in particular has
mathematical roots and should be understood by all computer programmers
(not just Bertrand Meyer devotees). To a lessor extent, the term precondition
has become established enough that it can be said to have a life of its own. It is
necessary to explain these terms because they are so widely used in computer
programming, but I do so with great trepidation. The advocates for Design by
Contract have been known to wield axes, as can be seen in the comments at the
bottom of Bug Id 4449383.12 This is the request for enhancement (RFE) for “full”
support of Design by Contract in the Java programming language. A less ambi-
tious RFE is Bug Id 4137085, which is a request for @pre, @post, and

11. See www.m-w.com.


12. Real hard-core Design by Contract enthusiasts should do a very careful and thorough read of
what Dr. Gosling has to say in an excellent Bill Venner’s interview available at www.artima.com/
intv/gosling3.html. (Two of the more important pages in regards to Design by Contract are
gosling38.html and gosling310.html.) Gosling’s responses to Venner’s questions are
anything but the support for Design by Contract implied in some of the comments at the
bottom of this RFE. Gosling actually concluded that Design by Contract is infeasible given “the cur-
rent state of the art.” For detecting NullPointerException at least this is generally known to
be the case. If you have not already, I would heartily recommend that you stop now and at least read
www.artima.com/intv/gosling310.html, “Exception versus Wrong Result” which includes
Gosling’s compelling account (one that is oft repeated) of the maiden flight of the European Space
Agency’s unmanned Ariane 5 rocket. It exploded shortly after takeoff, which was about the same
time that the Java programming language (with exception handling) was being introduced to the pub-
lic. The full story can be found on any number of Web sites. The one that Dr. Gosling links to off of
his home page is java.sun.com/people/jag/Ariane5.html.

672 JAVA RULES


@invariant documentation tags. Neither are likely to ever be implemented
for reasons discussed in this section.13
How easy it is to forget that documentation comments and the javadoc
tool represent a significant step forward in the direction of Design by Contract in
a mainstream programming language. The language designers were very proud
of this fact, as can be seen in the following statement from Patrick Naughton’s
now out-of-print The Java Handbook.
Comments are so important that when we designed Java, we followed
the ideas of Donald Knuth, legendary programmer; author of the semi-
nal book Art of Computer Programming; inventor of Pascal and
WEB, a system for documenting Pascal; and who made a version of
comment that could be extracted automatically as interface documen-
tation.14

The javadoc tool and document comments in general have grown in sophisti-
cation along with the rest of the Java platform since Naughton wrote this back in
1996. Whoever follows in the footsteps of Dennis Ritchie and Dr. Gosling will
benefit from a great many predecessors, including the javadoc team at
Sun.15
Before discussing invariants and postconditions, there is one thing I would
like to point out about the example of testing a precondition in the “Programming
With Assertions” document. There is a misleading statement that suggests that
a client programmer could be responsible for an assertion failure. Here is the
example and misleading statement:
/**
* Sets the refresh rate.
*
* @param rate refresh rate, in frames per second.
* @throws IllegalArgumentException if rate <= 0 or

13. As of this writing, none of the tags in RFE 4137085 are even included in the list of proposed
tags at java.sun.com/j2se/javadoc/proposed-tags.html. Note, however, that Doug Lea
has expressed some support for the @invariant tag in a Java Forum discussion.
14. Patrick Naughton, The Java Handbook, (Berkeley: McGraw-Hill, 1996), 41.
15. No disrespect for Bjarne Stroustrup intended. I just do not think C++ is enough of a departure
from the C programming language to consider it to be an entirely new programming language. C# is
a copy of Java and no one dares claim to have “invented” it. Instead it has only a “chief architect.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 673


* rate > MAX_REFRESH_RATE.
*/
public void setRefreshRate(int rate) {
// Enforce specified precondition in public method
if (rate <= 0 || rate > MAX_REFRESH_RATE)
throw new IllegalArgumentException("Illegal rate: "
+ rate);
setRefreshInterval(1000/rate);
}

/**
* Sets the refresh interval (which must correspond to a
* legal frame rate).
*
* @param interval refresh interval in milliseconds.
*/
private void setRefreshInterval(int interval) {
// Confirm adherence to precondition in nonpublic method
assert interval > 0 &&
interval <= 1000/MAX_REFRESH_RATE : interval;

… // Set the refresh interval


}

Note, the above assertion will fail if MAX_REFRESH_RATE is greater


than 1000 and the client selects a refresh rate greater than 1000. This
would, in fact, indicate a bug in the library!16

Any suggestion that a client programmer could cause an assertion failure is dis-
turbing. In this case, the presumption is that MAX_REFRESH_RATE is in fact
set to 1000. Thus if the client “selects a refresh rate greater than 1000” the
public setRefreshRate(int rate) method will throw an Illegal-
ArgumentException . This is as it should be.
In the previous section on logic traps, there is an if-then-else statement
that embodies the idea that “everything is animal, vegetable, or mineral.” In
mathematical terms, this is referred to as an invariant (something that does not

16. Unascribed, “Programming With Assertions” document in the API docs for the 1.4 release, Pre-
conditions, Postconditions, and Class Invariants.

674 JAVA RULES


change). An internal invariant is short-lived. Here is an example from the core
API:

char c = s.charAt(0);
for (int i = 0; i < n;) {
assert c == s.charAt(i); // Loop invariant

The term loop invariant is not widely used, but this is a good example of a
short-lived invariant. It is obviously true the first time the loop is executed. The
responsible programmer is merely asserting that it is true for every iteration.
Here is another example of an internal (or short-lived) invariant:

boolean removed = list.remove(null);


assert removed;

In this case, the responsible programmer is asserting that there was in fact a
null element in the target list. There are a number of boolean methods in
the Collections Framework for which this same idiom would be very useful.
A class invariant is something that is always true. More precisely, class
invariants should not change as the result of invoking a method or constructor.
They should always be documented. For example,
A buffer is a linear, finite sequence of elements of a specific primitive
type. Aside from its content, the essential properties of a buffer are its
capacity, limit, and position:

A buffer's capacity is the number of elements it contains. The


capacity of a buffer is never negative and never changes.

A buffer's limit is the index of the first element that should not
be read or written. A buffer's limit is never negative and is
never greater than the its capacity.

A buffer's position is the index of the next element to be read


or written. A buffer's position is never negative and is never
greater than its limit.

ASSERTIONS, EXCEPTIONS, AND LOGGING 675


Invariants

The following invariant holds for the mark, position, limit, and capacity
values:

0 <= mark <= position <= limit <= capacity

A newly-created buffer always has a position of zero and a mark that is


undefined. The initial limit may be zero, or it may be some other value
that depends upon the type of the buffer and the manner in which it is
constructed. The initial content of a buffer is, in general, undefined.

This is an excellent example of how best to document class invariants. It is


excerpted from the API docs for the java.nio.Buffer class. These API
docs make it look as if there were an @invariant tag already, but this effect
was achieved as follows.

*
*
* <h4> Invariants </h4>
*

Others may wish to use this same documentation technique until such time as an
@invariant tag is introduced. (I don’t want to sound like a fickle lover, but the
java.nio package is from design to documentation is unquestionably the
single best core API package ever developed for the Java platform.)
The following assertions are used to test the class invariant that the index of
the next element to be read or written (the position) should always be less
than the both the limit and capacity of a buffer.

assert (position <= capacity);


assert (position <= limit);

Because these are private fields, the responsible programmer is in effect


checking to see if he or she has made a mistake.
There is an open question of when it is best to test class invariants, either
before or after a method is invoked. The “Programming With Assertions” docu-
ment in the 1.4.1 release has this to say:
A class invariant…should be true before and after any method com-
pletes. For example, suppose you implement a balanced tree data

676 JAVA RULES


structure of some sort. A class invariant might be that the tree is bal-
anced and properly ordered.

The assertion mechanism does not enforce any particular style for
checking invariants. It is sometimes convenient, though, to combine
the expressions that check required constraints into a single internal
method that can be called by assertions. Continuing the balanced tree
example, it might be appropriate to implement a private method that
checked that the tree was indeed balanced as per the dictates of the
data structure:

// Returns true if this tree is properly balanced


private boolean balanced() {
...
}

Because this method checks a constraint that should be true before


and after any method completes, each public method and constructor
should contain the following line immediately prior to its return:

assert balanced();

It is generally unnecessary to place similar checks at the head of each


public method unless the data structure is implemented by native meth-
ods. In this case, it is possible that a memory corruption bug could cor-
rupt a “native peer” data structure in between method invocations. A
failure of the assertion at the head of such a method would indicate
that such memory corruption had occurred. Similarly, it may be advis-
able to include class invariant checks at the heads of methods in
classes whose state is modifiable by other classes. (Better yet, design
classes so that their state is not directly visible to other classes!).17

Basically your choice is either at the beginning or end of a method (native


methods and public fields aside). There is no consensus for asserting that
class invariants are true immediately before returning, not even in the core API.
For example, the java.nio programmers are asserting that class invariants
are true at the beginning of a method. They are also asserting that class invari-
ants are true in non-public methods. From what I can surmise, the

17. Unascribed, “Class Invariants.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 677


java.nio programmers are asserting that class invariants are true at the
start of any method that assumes so. Isn’t that the whole point? I do not under-
stand why you would limit such assertions to only public methods. The impli-
cation is that client programmers are somehow responsible for assertion errors,
and that should never be the case.
If a precondition is something that must be true before a method or con-
structor is invoked and a class invariant is something that is always true, then a
postcondition is something that must be true after a method is invoked. In
other words, a postcondition is what the method does. For example,

public synchronized Object clone() {


try {
Hashtable t = (Hashtable)super.clone();

assert t.equals(this);
return t;
} catch (CloneNotSupportedException e) {
assert false;
}
}

This is the clone() method for a hash table, which should equal the current
object after cloning (though the API docs do not require this). Documenting
“postconditions” is therefore synonymous with writing documentation comments
for a method or constructor (at least to the extent that the Java programming
language supports Design by Contract). Coding a postcondition assertion is
more or less like testing a method every time it is executed. There are few if any
postcondition assertions in the core API because Sun maintains a comprehen-
sive test suite.
Postcondition assertions may require saving data at the beginning of a
method. Suppose that the API docs for a method or constructor guarantee that
passing a mutable object is safe because the reference is only stored in a local
variable and is not modified as a result of invoking the method. The reason for
doing so would be so that client programmers do not have to make defensive
copies. This can be accomplished inexpensively using assertions. For example,

678 JAVA RULES


public static double mean(final int[] data, int scale) {
int[] copy = null;
BigDecimal mean = new BigDecimal("0");
synchronized(data) {
assert (copy = (int[])data.clone()) != null;
for (int i=0, n=data.length; i<n; i++) {
mean = mean.add(new BigDecimal(
Integer.toString(data[i])));
}
mean = mean.divide(new BigDecimal(
Integer.toString(data.length)), scale,
BigDecimal.ROUND_HALF_UP);
assert mean.intValue() <= Integer.MAX_VALUE;
assert Arrays.equals(data, copy);
}
return mean.doubleValue();
}

Saving the data is a side effect of evaluating the boolean expression in an


assert statement. Thus when assertions are disabled the int[] is not cop-
ied. Assignments in an assertion are not definite (for somewhat obvious rea-
sons). That requires initializing the copy with null. Note also that the
postconditions are executed immediately before returning.

6.2.2 assert false and Logic Traps (or Control-flow Invariants)


Assumptions are never good in computer programming. In fact, it is the
mark of a good programmer that he or she habitually builds what I like to call
logic traps rather than assume anything. The “Programming With Assertions”
document calls these control-flow invariants, but I have been calling them
logic traps for so long it is hard for me to think of them as anything else. Here
is an example,

if (thing instanceof Animal) {…}


else if (thing instanceof Vegetable) {…}
else if (thing instance of Mineral) {…}
else { //logic trap
assert false;
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 679


The assert false statement is special. Assuming assertions are enabled, it
is the equivalent of throw new AssertionError(). The alternative to this
logic trap would be to assume that the thing was an instance of the Mineral
class. The basis of that assumption is that everything is animal, vegetable, or
mineral. If you are utterly convinced of the validity of statements (or invariants)
such as “everything is animal, vegetable, or mineral,” then you are unlikely to
question an if-then-else statement such as the following.

if (thing instanceof Animal) {…}


else if (thing instanceof Vegetable) {…}
else { //everything else is Mineral

}

Computer programmers really should not make such assumptions. They are
potentially disastrous. Instead you should always use a logic trap to catch
the unknown (read test your assumptions). The expectation is that code gen-
erally should never fall into a logic trap, but logic traps have a way of teach-
ing you things about a system that you did not know before.
The other form of a logic trap in the Java programming language is the
default label in a switch statement. Here is an actual example from the
java.nio.charset.CoderResult class:

private static final int CR_UNDERFLOW = 0;


private static final int CR_OVERFLOW = 1;
private static final int CR_MALFORMED = 2;
private static final int CR_UNMAPPABLE = 3;

switch (type) {
case CR_UNDERFLOW: throw new BufferUnderflowException();
case CR_OVERFLOW: throw new BufferOverflowException();
case CR_MALFORMED: throw new MalformedInputException(length);
case CR_UNMAPPABLE: throw new
UnmappableCharacterException(length);
default:
throw new Error(); // ## assert false;
}

680 JAVA RULES


This example is useful as a reminder that logic traps should not necessarily
assert false. There is no exception to the rule that the responsible
programmer or development team must be in complete control of the code
that would result in an assertion failure. In this case, that is obviously the
case, and it looks as if the responsible programmer has slated this to become an
assertion in some future release. (Core API programmers have been throwing
Error, InternalError, and RuntimeException in logic traps prior to
the 1.4 release). This may seem like an overly cautious use of assertions, but to
leave out the default case in such a switch statement would be to assume
that no other coder results are ever added to the java.nio.charset pack-
age. A careless maintenance programmer could easily add a new coder result
and overlook the need for a corresponding change in the switch statement (the
convenience constants and switch statement are not juxtaposed in the actual
code as they are in this slightly modified example).
The term control-flow invariant is actually somewhat broader in meaning
than logic traps. It also applies to method and constructors that should not com-
plete normally (by reaching the closing brace of the method or constructor
body). Here is the outline of such a method in the java.awt.Component
class:

void createBufferStrategy(int numBuffers) {



if (numBuffers > 1) {
// Try to create a page-flipping strategy

try {

return; // Success
} catch (AWTException e) {
// Failed
}
}
// Try a blitting (but still accelerated) strategy

try {

return; // Success
} catch (AWTException e) {

ASSERTIONS, EXCEPTIONS, AND LOGGING 681


// Failed
}
// Try an unaccelerated blitting strategy

try {

return; // Success
} catch (AWTException e) {
// Failed
}
// Code should never reach here (an unaccelerated blitting
// strategy should always work)
throw new InternalError("Could not create a buffer strategy");
}

Here again the choice is between using assert false or unconditionally


throwing an exception, which are essentially the same thing. The only difference
is that assert false can be disabled in production. It is therefore imperative
that the responsible programmer or development team be in complete control of
the code. In this case, the “should never reach here” language is tentative
enough to make me think that using assert false would be a mistake. The
unconditional throwing of an exception at the bottom of a method is also dis-
cussed in 1.6.2 Result Types and the return Statement.

6.2.3 Catching CloneNotSupportedException


The single most common use of InternalError is in clone() methods.
For example,

public Object clone() {


try {
… code to clone object invokes super.clone() …
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}

This section argues that CloneNotSupportedException is a perfect can-


didate for assert false, but you could throw new ThreadDeath() and
it would not make a difference because CloneNotSupportedException

682 JAVA RULES


is never going to be thrown in a class that implements Cloneable.
CloneNotSupportedException is thrown “to indicate that the clone
method in class Object has been called to clone an object, but that the
object's class does not implement the Cloneable interface.”18 The problem is
that the clone() method in the Object class is also invoked as above using
super.clone() in overriding implementations of the clone() method.
This leads to a bizarre situation in which a client programmer knows his or her
class implements Cloneable, but nevertheless must catch CloneNot-
SupportedException. The question is what to do? I have done a thorough
study of the responses to this checked exception in the core API, and list them
here in their order of preponderance:
• Translate CloneNotSupportedException into an Error (the
most popular by far being InternalError, but Error per se is some-
times used). In a few examples, the original CloneNotSupported-
Exception is converted to a string and used as the detailed message for
the newly created error.
• Propagate the exception outward by declaring a clone() method that
throws CloneNotSupportedException. A few of these classes
included API documentation explaining that CloneNotSupported-
Exception is thrown if the current class does not “does not both (a)
implement the Cloneable interface and (b) define a clone method.”
Another class included a doc comment explaining that the exception is
never actually throw (which kind of contradicts the @exception tag).
• Squelch the exception (which is defined as an empty catch block)
• Return null (in a few cases, not before printing a stack trace)
• Translate the checked exception into a runtime exception, which is notably
called an InternalException (as opposed to an Internal-
Error) in the com.sun.tools.jdi package. Though low on the
totem poll, this is essentially the same as translating it into an error in that
none of the runtime exceptions used are likely to be caught. The original

18. API docs for the java.lang.CloneNotSupportedException class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 683


CloneNotSupportedException is sometimes converted to a
string and used as the detailed message for the newly created error.
• Clone the current object without invoking super.clone().
These responses are surprisingly varied to be all found in the core API. It
appears as if there was an effort to standardize on translating CloneNot-
SupportedException into an InternalError, a practice that is unfor-
tunately now being copied by application programmers. However, that response
accounts for no more than about half the classes in the core API that implement
Cloneable . I am especially surprised that propagating the checked exception
is so high on this list. I suspect this is a case of good soldiers following orders
(throwing checked exceptions that are not properly handled), but it could just as
easily be seen as an abuse of your client programmers not to take a good hard
look at this situation and bite the bullet, so-to-speak.
This is a perfect example of when using assert false is appropriate.
That doing so is safe can be seen in the fact that squelching the exception is
rather high up on this list. Furthermore, the choice of InternalError (about
the most serious JVM error there is) by so many core API programmers speaks
volumes. The simple fact of the matter is that if a JVM throws a CloneNot-
SupportedException in a class that actually does implement the
Cloneable interface, all bets are off. The very least of your concerns is going
to be how to finish cloning the current object. The reason why this is a perfect
example is that typing “implements Cloneable” in the class header is cer-
tainly something the responsible programmer is in complete control of. Nothing
is going to change the fact that the class implements Cloneable after it is
moved into the production environment. Overriding implementations of the
clone() should therefore look like this:

public Object clone() {


try {
… code to clone object invokes super.clone() …
} catch (CloneNotSupportedException e) {
assert false;
}
}

684 JAVA RULES


If nothing else, this can be seen as an intelligent compromise between squelch-
ing the exception and throwing InternalError.

NOTE 6.1
As the name applies, the following discussion is very general. More ex-
perienced programmers already familiar with the terminology of execu-
tion stacks and activation frames may want to skip over this section.

6.3 An Execution Stack Primer


Neither the JLS nor the JVMS actually use the term execution stack. The JLS
briefly discusses activation frames, but does not mention stacks. The Second
Edition of the JVMS has a section on Java virtual machine stacks, which were
referred to simply as Java stacks in the First Edition of the same specification.
Surprisingly, not even the FOLDOC has a generic term for the stack used to con-
trol execution of functions, subroutines, or methods. It merely says:
Perhaps the most common use of stacks is to store subroutine argu-
ments and return addresses. This is usually supported at the machine
code level either directly by “jump to subroutine” and “return from sub-
routine” instructions…19

I use the more formal term (and more generic when compared to “Java stack” or
“Java Virtual Machine stack”) to differentiate execution stacks from other com-
mon uses of the stack data structure, which in the case of the operand stack
are sometimes discussed in the same context. Another commonly used term is
call stack.20

19. Free Online Dictionary of Computing (FOLDOC) at foldoc.doc.ic.ac.uk/foldoc/


foldoc.cgi?stack.
20. In this section I recognize both call stack and call chain as alternative terms. In general, how-
ever, I never use call. The correct term is invoke, not call. In procedure-oriented programming lan-
guages such as C, programmers “call a function.” In object-oriented programming languages such
as Java, programmers “invoke a method.” This usage distinction is respected by most software
engineers and technical writers.

ASSERTIONS, EXCEPTIONS, AND LOGGING 685


The term stack is used here with exactly the same meaning as “a stack of
dishes,” only it is a stack of frames. An activation frame (a JLS term that is
referred to more simply in the JVMS as either a stack frame or frame) is a
small chunk of RAM used by a JVM to control the execution of a method. As
explained in the JLS, when a method is invoked
…a new activation frame is created, containing the target reference (if
any) and the argument values (if any), as well as enough space for the
local variables and [operand] stack for the method to be invoked and
any other bookkeeping information that may be required by the imple-
mentation (stack pointer, program counter, reference to previous acti-
vation frame, and the like).21

The reference to the target object and argument values are stored in the local
variable array. The local variable array and operand stack are the two most
important components of an activation frame. Figure 6.1 illustrates an execution

21. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java Language Specification, Sec-
ond Edition (Boston: Addison-Wesley, 2000), §15.12.4.5, “Create Frame, Synchronize, Transfer
Control.”

686 JAVA RULES


(or call) stack. The invoking method (or invoker for short) is just another way of

Figure 6.1 An Execution Stack

referring to the previous activation fame on the execution stack.


On the Java platform, a stack may be either a Java virtual machine stack or
a native method stack. There is one Java virtual machine stack for every
thread. A native method stack (alternatively referred to as either a “conven-
tional” stack or a C stack) is used to execute native methods written in the C
programming language. Every thread in a classic JVM has two stacks, a Java vir-
tual machine stack and a native method stack. The HotSpot VM has elimi-
nated the need for a conventional stack; native methods are now executed on
the same Java virtual machine stack as other methods.
Every frame represents a method that has not yet completed. A new activa-
tion frame added to the top of the stack each time a method is invoked. Class
instance creation expressions also add a frame to the stack in order to invoke
the special initialization method <init>. From a programmer’s perspective, a
stack is nothing more than nested method invocations. In that sense, a stack is
sometimes referred to as a method invocation chain (or call chain). Frames
are removed from the stack (or “discarded”) when a return statement is exe-
cuted, execution falls through to the closing brace (i.e., a method completes nor-

ASSERTIONS, EXCEPTIONS, AND LOGGING 687


mally), or an exception is thrown. (Alternatively activation frames are said to be
pushed onto and popped off a stack). When a frame is removed from the
stack, the previous frame becomes the current frame. If there is no previous
frame (for example, when the main method completes), the thread for that
stack “dies.”
As shown in Figure 6.1, the first method invoked is at the bottom of the
stack. In a single-threaded application this is always the main method. New
frames are added to the top of the stack. There are three important terms
defined by the frame at the top of a stack:
• Current (activation) frame
• Current method
• Current class
The current frame is the frame at the top of the stack. It is the only active
frame on the stack. The method executing in that frame is the current method.
The class in which the current method is declared is the current class. I must
warn the reader, however, that many software engineers and technical writers
(most notably the authors of the JVMS) think of new frames as being added to
the bottom of a stack. The following quote from the JVMS is representative.
On abrupt completion, the [current frame] is popped, reinstating the
frame of the invoking method. The exception is then rethrown in the
context of the invoker's frame and so on, continuing up the method
invocation chain. If no suitable exception handler is found before the
top of the method invocation chain is reached, the execution of the
thread in which the exception was thrown is terminated. 22 [emphasis
added]

I would have used down and bottom in the same context. To some extent, this
discussion is irrelevant because the memory allocated for frames may not even
be contiguous. In that case, stacks are largely conceptual.
However well established, the notion that new frames are added to the bot-
tom of a stack is absurdly counterintuitive, and I refuse to go along with it. The

22. Tim Lindholm and Frank Yellin, The Java Virtual Machine Specification, Second Edition,
(Boston: Addison-Wesley, 1999), §3.10, “Exceptions.”

688 JAVA RULES


top of the stack becomes the bottom, and vice versa. And for what purpose?
More important than the opinion of any one software engineer or technical writer
on this subject is the output from the printStackTrace() method in the
Throwable class. It represents new frames as being added to the top of the
stack. For example,

class Test {
public static void main(String[] args) {
a();
}
static void a() {
b();
}
static void b() {
c();
}
static void c() {
int frameCount = Thread.currentThread().countStackFrames();
System.out.println("there are " + frameCount +
" frames on the stack");
throw new RuntimeException();
}
}

Executing this program prints

there are 4 frames on the stack


Exception in thread "main" java.lang.RuntimeException
at Test.c(Test.java:12)
at Test.b(Test.java:9)
at Test.a(Test.java:6)
at Test.main(Test.java:3)

(Although countStackFrames() was deprecated in the 1.2 release, invok-


ing the method is still instructive for programmers learning about stack frames
for the first time.) If for no other reason than the printStackTrace()
method, new activation frames in the Java programming language should be
thought of as being added to the top of a stack.
If the current method at the top of a stack throws an exception, the activa-
tion frame is discarded (or popped off the stack, if you prefer). At this point, the

ASSERTIONS, EXCEPTIONS, AND LOGGING 689


exception is said to be “rethrown in the context of the invoker’s frame.”23 If no
matching catch clause is found in the exception table of the invoker, that acti-
vation frame is also discarded. The same is true of all the intervening methods
between the point of origin and the method in which an exception is caught. Two
important things happen before an activation frame is discarded:
• If the method is executing a try-finally or try-catch-finally
at the point of the abrupt completion, the finally clause is executed.
• If the method was synchronized or executing a synchronized
statement at the point of the abrupt completion, held locks are released.
This process is sometimes referred to as unwinding a thread. It is interesting to
note that the implementation of the now deprecated stop method in the
Thread class is nothing more or less than:

{throw new ThreadDeath();}

Thus it is the propagation of an uncaught exception that “kills” the thread by pop-
ping all of the frames off the stack. If the bottom of the stack is reached and no
matching catch clause has been found in any of the methods, the JVM uses
the thread one last time to invoke the default exception handler (which is dis-
cussed in 6.10 Uncaught Exceptions).

6.4 The Exception Mechanism


Exception handling in the Java programming language is a model of simplicity.
The idea of “throwing” and “catching” an exception, however, makes exception
handling sound complicated. Authors have tried a number of different tricks to
explain what “throwing” an exception means. The following was written by none
other than Patrick Naughton in the The Java Handbook:
The runtime throws an exception. We say throw because if you think of
it as a hot potato that the code must catch and deal with immediately,
then you’ll get the right idea.24

23. Tim Lindholm and Frank Yellin, §3.10, “Exceptions.”


24. Patrick Naughton, The Java Handbook, 167.

690 JAVA RULES


Another technical writer hopes to clarify what it means to throw an exception by
explaining that it is “really just throwing an object.”25 The truth is that nothing is
actually “thrown” at all. Nor does the catch clause actually catch an exception.
Baseballs are thrown and caught. In the Java programming language, excep-
tions result in an immediate transfer of control to a catch clause that handles
the exception. A programmer should think “immediate transfer of control” any-
where “throw” is used, and “handle the exception” anywhere “catch” is used.
This more explicit language is used in the following quote from the JLS:
The Java programming language specifies that an exception will be
thrown when semantic constraints are violated and will cause a non-
local transfer of control from the point where the exception occurred to
a point that can be specified by the programmer. An exception is said
to be thrown from the point where it occurred and is said to be
caught at the point to which control is transferred.26
The following is another example of the JLS explaining “throwing an exception” in
terms of a transfer of control:
When an exception is thrown, control is transferred from the code that
caused the exception to the nearest dynamically-enclosing catch
clause of a try statement that handles the exception.27

Why is this transfer of control characterized as “throwing” and “catching” an


exception? You would have to ask Dr. Gosling. His answer would likely be that
C++ uses the same terminology. Other programming languages raise and
handle exceptions, rather that throw and catch them. What else are you
going to do with an exception (a Throwable object) to get it from the point-of-
origin to either a dynamically enclosing catch clause or the default exception
handler? What keywords would you use?
In order to understand how the exception handling mechanism works it is
important to understand the concept of dynamic enclosure and how that

25. Mary Campione and Kathy Walrath, The Java Tutorial: Object-Oriented Programming for
the Internet, Second Edition (Reading: Addison-Wesley, 1998), “The catch Block(s).”
26. Gosling et al., Introduction to Chapter 11, “Exceptions.”
27. Gosling et al., §11.3, “Handling an Exception.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 691


relates to an execution stack. A catch clause is said to dynamically enclose
the code in a try block. Dynamic enclosure is defined as follows in the JLS:
A statement or expression is dynamically enclosed by a catch
clause if it appears within the try block of the try statement of which
the catch clause is a part, or if the caller of the statement or expres-
sion is dynamically enclosed by the catch clause. 28

The concept of dynamic enclosure is important because it includes all of the


code that is executed as the result of evaluating the method invocation or class
instance creation expressions in a try block. Hence the adjective “dynamic”
because the code in the body of a method or constructor invoked does not actu-
ally appear within the opening and closing braces of the try block. There are
two points to remember in order to fully understand how this term is used:
• Although referred to as a “dynamically” enclosing catch clause, this term
also refers to the code that actual does appear between the opening and
closing braces of the try block. In other words, the catch clause(s) in a
try statement “dynamically” enclose the try block of the same try
statement
• A catch clause is dynamically enclosing even if the type of the Throw-
able object is not assignable to the type of the exception-handler parame-
ter for that particular catch clause. In other words, a dynamically enclos-
ing catch clause is not necessarily the matching catch clause.
Dynamic enclosure is best understood in terms of the frames on a stack. For
any given frame on the stack, if the program counter is pointing to a bytecode in
a try block with catch clauses, those catch clauses dynamically enclose all
of the code in that try block as well as all of the code executed in all of the
frames that are stacked on top of that frame. Program counters are dis-
cussed below.
The term dynamically enclosure is further defined to mean methods that
are executed on the same stack (emphasis on same). This is an important
point to grasp otherwise naive programmers tend to think that catch clauses
in the top-level exception handler of a main method dynamically enclose all of

28. Gosling et al., §11.3, “Handling of an Exception.”

692 JAVA RULES


the code in a multi-threaded application, which is not the case. This subject is
discussed further in 6.10.1 Top-level Exception Handlers below.
The catch clauses that dynamically enclose the code in a try block are
best thought of as overloaded catch methods that are inlined by the compiler.
This analogy holds on a number of different levels:
• Instead of using per-class method dispatch tables, per-method exception
tables are used.
• Overloaded method “matching” and “matching” catch clauses use the
same terminology.
• The method invocation and exception handling conversion contexts are
comparable in that the type of the Throwable object is implicitly con-
verted to the type of the exception handler parameter.
• The catch block (versus a method body) is then executed.
In the case of finally clauses (which as implemented in a JVM are indistin-
guishable from catch clauses), the JVMS actually refers to them as “embedded
subroutines.”29
I will now take a closer look at exception tables in order to define the term
matching catch clause. Every method in which one or more try statements
are coded has an exception table, even a try-finally statement that has no
catch clauses requires an exception table. As suggested above, these per-
method exception tables are used very much like per-class method dispatch
tables, only the tables have different keys that map to exception handlers and
finally blocks instead of to methods. For example,

import java.io.*;
import java.util.logging.Logger;
class Test {
static Logger logger = Logger.global;
public static void main(String[] args) throws IOException {
System.out.println(isJavaFile(args[0]));
}
static boolean isJavaFile(String name) throws IOException {
DataInputStream in = null;
try {

29. Tim Lindholm and Frank Yellin, §7.13, “Compiling finally.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 693


in = new DataInputStream(new FileInputStream(name));
int magicNumber = ;
if (in.readInt() != 0xCAFEBABE)
throw new Error("not a class files");
int minorVersion = in.readInt();
int majorVersion = in.readInt();
System.out.println("0x" +
Integer.toHexString(magicNumber).toUpperCase());
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
logger.throwing("Test", "isJavaFile(String name)", e);
throw e;
} finally {
if (in != null) {
try {
in.close();
} catch(IOException e) { } //ignore
}
}
return false;
}
}

Executing this program passing "Test.class" as a command-line argument


prints

0xCAFEBABE
true

The decompiled code (obtained by typing javap -c Test at the DOS prompt)
for the isJavaFile(String name) method includes the following excep-
tion table.

Exception table:
from to target type
2 65 73 <Class java.io.FileNotFoundException>
2 65 81 <Class java.io.IOException>
2 65 95 any
67 70 95 any
73 79 95 any
81 100 95 any
109 113 116 <Class java.io.IOException>

694 JAVA RULES


Table 6.1 is a more formal presentation of the exception table in this example.
Instead of the from, to, target, and type column headers used by the class
file disassembler, it uses the start_pc, end_pc, catch_type, and
handler_pc data names from the class file format as described in the JVMS.
Program counter values are offsets from the beginning of a method. They are of
little use until added to the actual address of the method. Therefore, in order to
understand exception tables such as this you must know something about pro-
gram counters.
Inside of a class file the code that implements a method is stored in a
byte[] known as the code array. Every frame on the stack has a program
counter, the value of which is an address of some opcode (or machine instruc-
tions) in the code array. The value of the program counter for the current
method at the top of the stack is stored in the program counter (or pc) regis-
ter. (If a native method is executing the value of the pc register is undefined.)

Table 6.1 Example of a Per-Method Exception Table


Table Key

start_pc end_pc catch_typea handler_pc


2 65 FileNotFound- 73
Exception
2 65 IOException 81
2 65 any 95
67 70 any 95
73 79 any 95
81 100 any 95
109 113 IOException 116
a. Instead of “any” the value of catch_type for a finally clause in an actual class file is zero.

After each opcode is executed, the interpreter increments the value of the pc
register. The number of bytes incremented depends on the opcode. Thus the pc

ASSERTIONS, EXCEPTIONS, AND LOGGING 695


register is always pointing to the next opcode to execute. Note that this is an
address that is being incremented, not an index.
The start_pc, end_pc, and handler_pc columns in an exception
table are offsets from the beginning of the code array. After the class is loaded
and initialized, the address of the code array is added to these offsets. The
main try block in this example is from code[2] to code[64]. The
FileNotFoundException handler is from code[73] to code[79]. The
IOException handler is from code[81] to code[94]. The finally
block begins at code[103], not code[95] as the exception table would lead
you to believe. The code from code[95] to code[102] is some extra code
that is required when the try or catch block throws an exception. (There are
some other quirks related to the finally bock entries in this example that are
way too complicated to explain in this section.) Including the start_pc and
end_pc in the exception table is what makes it possible to store the catch
and finally clauses of all of the try statements for a given method or con-
structor in the same exception table.
If an exception is thrown during execution of the method, the value of the pc
register is compared to start_pc and end_pc. The start_pc is inclusive;
the end_pc is exclusive. A matching catch clause is one in which the
start_pc is equal to or greater than the pc register, the pc register is less
than end_pc, and the (class) type of the Throwable object is assignable to
the catch_type.
The exception table is also used to implement finally clauses. Those
are the entries in which the catch_type is any. As stated in the JVMS:
As implemented by the Java virtual machine, each catch or
finally clause of a method is represented by an exception handler.
An exception handler specifies the range of offsets into the Java virtual
machine code implementing the method for which the exception han-
dler is active, describes the type of exception that the exception han-
dler is able to handle, and specifies the location of the code that is to
handle that exception. An exception matches an exception handler if
the offset of the instruction that caused the exception is in the range of
offsets of the exception handler and the exception type is the same

696 JAVA RULES


class as or a subclass of the class of exception that the exception han-
dler handles.30

The handler_pc entries in an exception table are in the same textual order as
they appear in a method or constructor. Note that if try statements are nested
(defined as a try statement in a try block), the catch and finally
clauses of the innermost try statement is entered first. The try block in this
example that begins at code[109] is not nested because it is coded in the
finally block of the enclosing try statement (not in the try block), and so
is listed in textual order. The fact that catch clauses are entered in textual
order is important because exception tables are searched from top to bottom.
If a matching catch clause is found, the value of handler_pc is used to
reset the pc register, and execution continues at that point.

NOTE 6.2
Unlike checked and unchecked exceptions, the differences between er-
rors, runtime exceptions, and all other (checked) exceptions is nothing
more than a programmer convention. More thought must be given to
the definition of these exception superclasses. Otherwise we are look-
ing at the very real possibility that they will become completely mean-
ingless. This should not be a controversial subject. The differences
between errors, runtime exceptions, and checked exceptions should
be precisely defined, unassailable, and easily understood. Like cracks
in a huge concrete damn, I already see evidence that this weakness in
the exception mechanism is threatening to inundate us, —not with wa-
ter—but with mass confusion (such as core API programmers translat-
ing runtime exceptions into errors).

30. Tim Lindholm and Frank Yellin, §3.10, “Exceptions.” I realize a lot of the material in this quote is
redundant. This is sometimes done deliberately to reassure the reader that my presentation of a
subject is in line with the specifications.

ASSERTIONS, EXCEPTIONS, AND LOGGING 697


6.5 The Throwable Class Hierarchy
This section provides an overview of the Throwable class hierarchy as shown
in Figure 6.2. Throwable is never directly subclassed. Doing so would

Figure 6.2 The Throwable Class Hierarchy

break catch(Exception e) catchall exception handlers, which is yet


another reason to prefer the more up-to-date catch(Throwable e) catchall
exception handler. All exceptions are instances of either the Exception or
Error superclasses.
As defined in the JLS the term exception class refers to any class in the
Throwable class hierarchy, including errors:
Exceptions are represented by instances of the class Throwable
and instances of its subclasses. These classes are, collectively, the
exception classes. 31

31. Gosling et al., §11.1, “The Causes of Exceptions.”

698 JAVA RULES


Likewise, the term standard exceptions refers to any of the exception classes
in the core API. It is necessary to explain the use of these terms because other-
wise one may assume they only refer to instances of the Exception class.
All of the exception classes in the Throwable class hierarchy are either
checked or unchecked exceptions. It is commonly thought that checked
exceptions are instances of the Exception class (minus instances of the
RuntimeException class), but that is not how the JLS defines these terms:
The unchecked exceptions classes are the class Runtime-
Exception and its subclasses, and the class Error and its sub-
classes. All other exception classes are checked exception classes.32

The significance of this to application programmers is that Throwable is a


checked exception. There are at least two uses of the throw statement that
are inexplicable unless you are aware of the fact that Throwable is a checked
exception.
• The initCause(Throwable cause) method added to Throwable
in the 1.4 release returns Throwable presumably to support method invo-
cation chaining. If method invocation chaining is actually used when creating
a runtime exception or error, however, a cast operator is required. The
fillInStackTrace() method suffers from essentially the same prob-
lem.
• Methods that have a Throwable type parameter inadvertently convert
unchecked exceptions into checked exceptions. This requires the use of a
(RuntimeException) or (Error) cast operator when rethrowing the
exception
Here is an example of the first bulleted item from the core API:

catch (URISyntaxException ex) {


throw (MalformedURLException) new MalformedURLException(
"invalid URL string: " + str).initCause(ex);
}

A compiler error is generated if the (MalformedURLException) cast


operator is omitted because, as far as the compiler is concerned, the

32. Gosling et al., §11.2, “Compile-Time Checking of Exceptions.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 699


Throwable object returned by the initCause method is an entirely different
checked exception that must be either caught or declared in the throws
clause. The alternative is not to use method invocation chaining, in which case it
takes three lines of code to throw the same exception: one line to create the
exception, another to invoke the initCause(Throwable cause) method,
and one to throw the exception. There is an example of this below. Program-
mers use both approaches.
The fillInStackTrace() is intended to be invoked when rethrowing the
same exception. The result type is Throwable, but invoking this method in a
throw statement also requires the use of a cast operator. For example,

class Test {
public static void main(String[] args) throws Exception {
try {
throw new Exception();
} catch(Exception e) {
…other code…
throw e.fillInStackTrace();
}
}
}

Attempting to compile this program generates the following compiler error:

Test.java:6: unreported exception java.lang.Throwable; must be


caught or declared to be thrown
throw e.fillInStackTrace();
^
1 error

The problem again is that Throwable is a completely different checked excep-


tion.
The rethrowing of Throwable type method parameters is less common,
but even stranger looking the first time you encounter it. The following slightly
modified example of a method with a Throwable type parameter is from a
class in the java.io package. It is a convenience method for wrapping
checked exceptions in an IOException:

700 JAVA RULES


private static void throwException(Throwable throwable)
throws IOException {
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
} else if (throwable instanceof Error) {
throw (Error) throwable;
} else {
IOException ex = new IOException(
("unexpected exception type");
ex.initCause(throwable);
throw ex;
}
}

RuntimeException and Error must be rethrown separately because they


are unrelated classes and (RuntimeException) and (Error) are the
only cast operators that can be used to convert a Throwable object from a
checked exception back into an unchecked exception. Whenever you see the
(RuntimeException) or (Error) cast operators used in a throw state-
ment, chances are that the exception being thrown is a Throwable type
method parameter.
What exactly is a “checked” exception? The “check” in checked exceptions
refers to the following three-part compile-time check for an exception handler
that is performed for every method and constructor:
• The declaration of every method or constructor invoked is checked to see if
it includes a throws clause. Because it is possible for a programmer to
name unchecked exception classes in the throws clause of a method or
constructor header, the compiler must also determine if the exception
classes named in the throws clause are checked or unchecked excep-
tions. (Bug Id 1240863 is of historical interest here.)
• If any of the methods or constructors invoked throws a checked excep-
tion, or if there is a throw statement coded anywhere in the method or
constructor body that explicitly throws a checked exception, then the com-
piler checks to see if there is a matching catch clause (or exception han-
dler) as defined in the previous section.
• If there is no matching catch clause for any of the checked exceptions
thrown and the method or constructor is not declared to throw the excep-

ASSERTIONS, EXCEPTIONS, AND LOGGING 701


tion (or more precisely, the checked exception is not assignable to one of
the exceptions in the throws clause), an unreported exception
compiler error is generated which includes the fully qualified name of the
checked exception class followed by “must be caught or declared to be
thrown” (at least when using the javac compiler).
None of these checks are performed for native methods. A native
method therefore can throw checked exceptions not named in the throws
clause. It is the responsibility of the programmer who implements a native
method to make sure this does not happen.
It should be understood that the throws clause in a method or constructor
header serves no runtime purpose. It is only used by the compiler to check for
exception handlers. As stated in the JLS:
This compile-time checking for the presence of exception handlers is
designed to reduce the number of exceptions which are not properly
handled.33

This sentence forms the philosophical basis upon which I build my definition of
runtime exceptions and errors (the two baseclasses for unchecked exceptions).
Ideally all exceptions would be checked. As obvious as this may seem, it is
profoundly important in understanding the Throwable class hierarchy. There
are only three reasons for exempting an exception from these compiler checks:
• Exceptions that are potentially thrown in every method: Either catching
or declaring that these exceptions are thrown would overburden the excep-
tion mechanism, making it a hindrance rather than a help to application pro-
grammers
• Exceptions that are not thrown at all: If never thrown, then never caught.
Such exceptions would make for meaningless entries in the throws clause
of method and constructor headers, in effect overburdening the exception
mechanism for less obvious reasons
• Catastrophic (or fatal) exceptions thrown during system initialization as
the result of something an end user does: The only point in catching these
exceptions is to explain to the end user what they are doing wrong. This can

33. Gosling et al., §11.2, “Compile-Time Checking of Exceptions.”

702 JAVA RULES


be accomplished by means of a top-level exception handler without the need
to declare that the exception is thrown in all of the intervening methods
Without discussing the particulars of any of the runtime exceptions or errors in
the core API, these three reasons for exempting an exception from the compile-
time check for an exception handler stand on their own. They form the basis for
defining the terms runtime exception and error from scratch. This is neces-
sary because neither the JLS nor the API docs are of much use in answering the
question as to why there are unchecked exceptions. Nor can one arrive at the
correct answer by studying the Error and RuntimeException subclasses
in the core API. Mistakes have been made. None comes more readily to mind
than MissingResourceException in the java.util package, which
(for reasons discussed below) clearly should have been a checked exception. I
honestly believe that not even the inventors of the Java programming language
had a firm grasp of the fundamental differences between errors, runtime excep-
tions, and all other (checked) exceptions. That is a brazen assertion coming
from the rank and file, but there is documentary evidence that clearly suggests
that their understanding of this subject was limited.
The API docs for the RuntimeException class are worse than useless
in answering the question as to why there are unchecked exceptions:
RuntimeException is the superclass of those exceptions that can
be thrown during the normal operation of the Java Virtual Machine.34

What does this mean? To get inside of the head of the person who wrote this,
one must take a look at the API docs for the Error class:
An Error is a subclass of Throwable that indicates serious prob-
lems that a reasonable application should not try to catch. Most such
errors are abnormal conditions. The ThreadDeath error, though a
“normal” condition, is also a subclass of Error because most applica-
tions should not try to catch it.35

Juxtaposing these API docs, I conclude that the writer was only thinking of
unchecked exceptions when writing the API docs for the RuntimeException

34. API docs for the java.lang.RuntimeException class.


35. API docs for the java.lang.Error class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 703


class. That being the case, “those exceptions” should read “those unchecked
exceptions.” I base this on the reference to “the normal operation of the Java Vir-
tual Machine,” which must be compared to “abnormal conditions” in the API docs
for the Error class. Otherwise we are forced to conclude that checked excep-
tions are thrown during the “abnormal operation of the Java Virtual Machine.” If I
have learned anything over the past seven years it is that technical writers live in
glass houses, but there are times when I become forgetful and throw a stone
anyway.
The first problem in defining the term runtime exception is the adjective
runtime. The difference between runtime, run-time, and run time is some-
thing software engineers and technical writers must grapple with constantly. For
example,
Many of the operations and constructs of the Java programming lan-
guage can result in runtime exceptions. The information available to a
compiler, and the level of analysis the compiler performs, are usually
not sufficient to establish that such run-time exceptions cannot
occur…36 [emphasis added]

Notice the change from runtime to run-time in this JLS quote. As used in this
book, runtime usually means the runtime system (the only exception being the
term runtime exception); run-time is the compound adjective form of run
time; and run time simply means that a Java VM is executing (as opposed to
compile time). With this in mind, I will now explain my use of the term runtime
exception.
An important distinction must be made between extending the Runtime-
Exception class and throwing one of the “pre--existing” runtime exceptions
declared in the core API, particularly those in Table 1.6 Runtime Exceptions
Commonly Thrown by Argument Checks on page 150. No one argues that appli-
cation programmers should not throw runtime exceptions, only that they should
not declare them. In fact, the JLS has always said “Java programs can use the
pre-existing exception classes in throw statements…as appropriate.”37 Simi-

36. Gosling et al., The Java Language Specification, §11.2.2, “Why Runtime Exceptions are Not
Checked.”
37. Gosling et al., §11.5, “The Exception Hierarchy.”

704 JAVA RULES


larly, the NullPointerException class has always said “applications
should throw instances of this class to indicate other illegal uses of the null
object.”38 Runtime exceptions such as NullPointerException are rou-
tinely thrown by application programmers. Thus the term runtime exception
cannot mean that the exception is only thrown by the runtime system.
The alternative of run-time exception (as is used in some Java books), how-
ever, would indicate nothing more than the exception was thrown at run time.
That is meaningless because all exceptions are thrown at run time. As used in
Java Rules (and as it should be used everywhere else), the term runtime
exception means nothing more or less than an instance of the misnamed
RuntimeException class. Such exceptions are thrown by both the runtime
system and application programmers.
The remainder of this section explains why there are unchecked exceptions.
I believe this explanation to be mathematically precise, unassailable, and easy to
understand.
Any discussion of why there are unchecked exceptions must begin with a
careful reading of 11.2.2 Why Runtime Exceptions are Not Checked in the JLS,
which is quoted here in its entirety:
The runtime exception classes (RuntimeException and its sub-
classes) are exempted from compile-time checking because, in the
judgment of the designers of the Java programming language, having
to declare such exceptions would not aid significantly in establishing
the correctness of programs. Many of the operations and constructs of
the Java programming language can result in runtime exceptions. The
information available to a compiler, and the level of analysis the com-
piler performs, are usually not sufficient to establish that such run-
time exceptions cannot occur, even though this may be obvious to
the programmer. Requiring such exception classes to be declared
would simply be an irritation to programmers.

For example, certain code might implement a circular data structure


that, by construction, can never involve null references; the pro-
grammer can then be certain that a NullPointerException can-

38. API docs for the java.lang.NullPointerException as far back as the 1.0.2 release.

ASSERTIONS, EXCEPTIONS, AND LOGGING 705


not occur, but it would be difficult for a compiler to prove it. The
theorem-proving technology that is needed to establish such global
properties of data structures is beyond the scope of this specifica-
tion.39 [emphasis added]

This section in the JLS does not answer the question it poses. It is largely of his-
torical interest. This entire discussion focusses on runtime exceptions thrown by
a Java VM (which in the case of the HotSpot VM is C++ code) versus exceptions
thrown in the core API and support classes in the sun and com.sun packages.
This much is painfully obvious because of references to “the level of analysis the
compiler performs” and “theorem-proving technology.” The core API and support
classes use the same throw statement as application programmers and there-
fore do not require any “level of analysis” or “theorem-proving technology.” Pre-
sumably, runtime exceptions thrown in the core API and support classes (such
as the NoSuchElementException thrown in the nextElement()
method of an Enumeration) are covered by “in the judgment of the designers
of the Java programming language, having to declare such exceptions would not
aid significantly in establishing the correctness of programs.” This statement,
however, does nothing to enhance the understanding of why runtime exceptions
are unchecked.
The essential specification in this section of the JLS is “not sufficient to
establish that such run-time exceptions cannot occur.” In other words, pro-
grammers should neither catch nor declare that a method or construc-
tor throws exceptions that are not actually thrown. This may sound trivial,
but it is actually the raison d'être for all runtime exceptions as well as for most
errors. In the JLS, such exceptions are said to “violate the semantic constraints
of the Java programming language.”40 Programmers commonly refer to these
as bugs or programmer errors. Rather than catching such an exception, the bug
should be fixed. The classic example of this is ArithmeticException.
Rather than catching ArithmeticException, an if (divisor == 0)

39. Gosling et al., §11.2.2, “Why Runtime Exceptions are Not Checked.”
40. Gosling et al., Introduction to Chapter 11, “Exceptions.”

706 JAVA RULES


check is added before the division operation is executed. There should be a
comparable fix for every runtime exception. For example,
• Use the instanceof operator to fix a ClassCastException
• Use a bounds check to fix an IndexOutOfBoundsException
• Comply with preconditions to fix an IllegalArgumentException
• Check for a negative dimension expression before creating an array to fix a
NegativeArraySizeException
• Invoke the hasNext() method before next() when iterating over the
elements of a set or list to fix a NoSuchElementException. Likewise,
invoke hasMoreElements() before nextElement() when using an
Enumeration instead of an Iterator.
• Check for null to fix a NullPointerException
• Rethink your program flow if an IllegalStateException is thrown
• Do not invoke methods that (unconditionally) throw an Unsupported-
OperationException
The list goes on and on.
Such “runtime” exceptions are only thrown during development and unit test-
ing. They are not thrown in production unless there is a bug in the code. This
explains why runtime exceptions are not caught. Declaring that a method or con-
structor throws a runtime exception is tantamount to admitting that it includes
a bug. If documented at all, runtime exceptions are only documented in the
method or constructor that actually throws them using the @throws clause.
Can you see the logic of this? If these were checked exceptions they would have
to be declared in the throws clause of other methods or constructors. The
problem with that is the expectation that such exceptions are fixed during devel-
opment and unit testing. In hindsight, RuntimeException was an unfortu-
nate choice of a name. Life would have been a lot easier for all of us had this
exception superclass been named DebuggingException instead (or Bugs,
ProgrammerError, etc.). If you think of them as debugging exceptions,
the fact that they are only thrown during development and unit testing will be
much easier to remember.

ASSERTIONS, EXCEPTIONS, AND LOGGING 707


One very important point to grasp is that runtime exceptions cannot
depend on class files, resource bundles, property files, policy files, char-
acter encodings, and the like. The reason for this is simple. The production
files may not be the same as those used during development and unit testing. If
the files are not the same, there exists the possibility that the runtime exception
will be thrown in production. This is a serious problem because runtime excep-
tions are exempted from the compiler check for an exception handler on the
basis that they are not thrown in production. This explains why Missing-
ResourceException should have been declared as a checked exception.
While it is possible that missing keys and null properties could be a program-
mer error because the key or property name is misspelled, this does not change
the fact that the exception may still be thrown in production even though it was
not thrown during development and unit testing. Therefore all such exceptions
must be either errors or checked exceptions. If the file is a designated part of
the core API or host operating system, a system configuration error is thrown.
Otherwise a checked exception should be thrown.
Mistakes in the declaration of runtime exceptions such as Missing-
ResourceException can be easily detected. If runtime exceptions are not
thrown in production, you would not expect to find exception handlers for them.
Programmers will always find a reason to catch an exception (any exception),
but routinely catching a runtime exception is a sure sign that something is funda-
mentally wrong. For example, there are exception handlers for Missing-
ResourceException strewn throughout the core API, each a testament to
the fact that this should have been a checked exception. To the extent that
MissingResourceException encourages the notion that runtime
exceptions are explicitly caught, it has done more damage (by way of
distorting the perception of the Throwable class hierarchy) than any
other exception class in the core API. I do not think it is possible to exagger-
ate the damage that this exception class has done. It is a mistake that Java pro-
grammers will have to life with for the duration, but it should not be allowed to
further erode the distinction between checked and unchecked exceptions.
Another runtime exception that is very similar to MissingResource-
Exception in this regard is SecurityException. Instead of resource

708 JAVA RULES


bundles, SecurityException depends on policy files which have an even
greater probability of being different in production. As would be expected, there-
fore, there are a substantial number of exception handlers for this runtime
exception.
There is only one other exception in the core API of which I am aware that
miserably fails this runtime exception authentication test. That is Number-
FormatException, which has more exception handlers (in the core API at
least) than any other runtime exception. It should never have been declared as
an IllegalArgumentException subclass. Methods that throw this excep-
tion have String type arguments. The only “illegal” string type argument that I
can think of would be an empty string. This is actually a parsing error. The differ-
ence is obvious looking at source code. Normally argument checks are coded at
the top of a method, but NumberFormatException is thrown while parsing
the argument. The simple fact of the matter is that NumberFormat-
Exception should have been declared as a checked exception.
Parsing exceptions present an unusual problem when trying to understand
the difference between runtime and checked exceptions. When the method or
constructor argument is a string literal, parsing exceptions are like runtime
exceptions in that if they are not thrown during development and unit testing,
they will not be thrown in production. Under these circumstances, the exception
mechanism may seem “unimaginably inconvenient.”41 The problem is that the
same method or constructor may parse strings that are passed as variables and
originate from command-line arguments, input streams, and the like. This
explains why parsing exceptions must be declared as checked exceptions. In
some cases, however, there is a reasonable expectation that client program-
mers will always be passing string literals (or possibly strings that are the result
of a string concatenation operation) in which case it is safe for the method or
constructor to convert a checked parsing exception into an unchecked
IllegalArgumentException. For example,

try {
initialize(mimeType, null, this.getClass().getClassLoader());

41. Gosling et al., §8.4.4, “Method Throws.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 709


} catch (MimeTypeParseException mtpe) {
throw new IllegalArgumentException("failed to parse:" +
mimeType);
}

This try statement is from a java.awt.datatransfer.DataFlavor


constructor. If that is not the case, however, they way to deal with this problem
as a client programmer heretofore has been to completely ignore parsing excep-
tions (either runtime or checked) when passing string literals. For example,

// predefined tables should contain correct grammar


try {
result = new RuleBasedCollator(CollationRules.DEFAULTRULES);
} catch (ParseException bar) {
// do nothing
}

This example is from a method in java.text.Collator. It involves an


inlined constant, but the same rule of ignoring the parsing exception is applied.
As of the 1.4 release, however, this exception handler should assert false.
Because the client programmer is passing a string literal he or she may be
rightly said to be in complete control. Core API programmers differ on what to
do when strings are read from resource bundles. Some ignore the parsing
exception assuming the file is the same as it was during development and unit
testing. Assumptions are never good in computer programming, however. The
correct thing to do is to handle the exception. To use assert false would be
a gross mistake. If a problem does arise it will be because the wrong file is being
used in production, and assertions are disabled in production. The main reason
for this discussion of parsing exceptions is to point out that methods and con-
structors that throw NumberFormatException are rarely passed string lit-
erals, which is all the more reason why this exception class should not have
extended IllegalArgumentException.
I would argue that exceptions such as MissingResourceException,
SecurityException , and NumberFormatException (that should have
been declared as checked exceptions) should be routinely declared in throws
clauses as a way of compensating for the mistake of having declared them as
runtime exceptions. Notably, a significant number of methods in the core API

710 JAVA RULES


have always declared that these runtime exceptions are thrown. Among them
are the class methods for parsing primitive numeric types such as
Integer.parseInt(String s) in the primitive type wrapper classes
which are declared to throw NumberFormatException.
The reason why errors are not checked is complicated by the fact that there
are three fundamentally different kinds of errors, which I refer to as every
method errors, non-occurring errors, and recurring end-user errors.
Again, any discussion of this subject must begin with a careful reading of 11.2.1
Why Errors are Not Checked in the JLS, which is quoted here in its entirety:
Those unchecked exception classes which are the error classes
(Error and its subclasses) are exempted from compile-time checking
because they can occur at many points in the program and recov-
ery from them is difficult or impossible. A program declaring such
exceptions would be cluttered, pointlessly.42 [emphasis added]

It is interesting to contrast the “can occur at many points in the program” in this
specification to the “never occurs” in the following API docs for the Error
class.
An Error is a subclass of Throwable that indicates serious prob-
lems that a reasonable application should not try to catch. Most such
errors are abnormal conditions. The ThreadDeath error, though a
“normal” condition, is also a subclass of Error because most applica-
tions should not try to catch it.

A method is not required to declare in its throws clause any sub-


classes of Error that might be thrown during the execution of the
method but not caught, since these errors are abnormal conditions
that should never occur.43 [emphasis added]

This is the first indication that there is more than one kind of error. In fact, the
JLS is describing one kind of error and the API docs are describing another kind.
Errors that “can occur at many points in the program” are the LinkageError
and VirtualMachineError classes (excluding InternalError). These

42. Gosling et al., §11.2.1, “Why Errors are Not Checked.”


43. API docs for the java.lang.Error class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 711


every method errors are the only errors that “can occur at many points in [a]
program.” They are exempted from compile-time checking because almost
every method and constructor would either have to catch them or declare
that they are thrown.
The JLS defines all errors and runtime exceptions on the basis that they can
occur in almost every method:
The predefined exceptions that are not checked in this way are those
for which declaring every possible occurrence would be unimaginably
inconvenient:

• Exceptions that are represented by the subclasses of class


Error, for example OutOfMemoryError, are thrown
due to a failure in or of the virtual machine. Many of these
are the result of linkage failures and can occur at unpredict-
able points in the execution of a program. Sophisticated pro-
grams may yet wish to catch and attempt to recover from
some of these conditions.
• The exceptions that are represented by the subclasses of
the class RuntimeException, for example Null-
PointerException, result from run-time integrity
checks and are thrown either directly from the program or in
library routines. It is beyond the scope of the Java program-
ming language, and perhaps beyond the state of the art, to
include sufficient information in the program to reduce to a
manageable number the places where these can be proven
not to occur.44
This simply is not true, however. The only errors to which this specification
applies are LinkageError and VirtualMachineError (which is pre-
cisely why they are called “every method errors”). Furthermore, it applies to a
precious few runtime exceptions. In fact, I believe NullPointerException
is the only runtime exception to which it applies. The fact that the designers of
the language never got beyond this point in their understanding of the fundamen-
tal differences between runtime exceptions and errors explains why recoverabil-
ity became an issue. They had to explain why NullPointerException is a
runtime exception instead of an error.

44. Gosling et al., §8.4.4, “Method Throws.”

712 JAVA RULES


The concept of recoverability, however, does not apply to runtime excep-
tions because they are fixed during development and unit testing; nor is the fact
that recovery is “difficult to impossible” a defining characteristic of every method
and non-occurring errors (the only two kinds of errors that existed at the time the
above specification was written). It is at best a secondary consideration. The
fact that recovery from some (but certainly not all) errors is “impossible” is only
a primary factor in the definition of recurring end-user errors . In short,
recoverability is a red herring. This is perhaps the most damning evidence
there is that the designers of the Java programming only partially understood
their own Throwable class hierarchy. Like all truly great programmers
must do from time to time they were flying by the seat of their pants.
The concept of every method errors applies to all programming languages
that throw (or raise) exceptions. Every programming language has the equivalent
of the virtual machine errors (OutOfMemoryError and StackOverFlow-
Error in particular). Beyond that, however, every method errors are designed
into the language. In the case of the Java programming language, the only other
every method errors are linkage errors, which are a consequence of dynamic
loading. The designers of the Java programming language were right in their deci-
sion to allow Error and RuntimeException to be extended because the
only unchecked exceptions that cannot be extended are every method errors.
The second kind of error described in the API docs for the Error class
“never occurs.” This is very different from linkage and virtual machine errors that
“can occur at many points in the program.” Examples of such non-occurring
errors in the core API include AssertionError and what I refer to as sys-
tem configuration errors such as FactoryConfigurationError and
TransformerFactoryConfigurationError. The bulk of these errors
are unspecified, which (as explained in the following subsection) means that
either Error or InternalError is thrown. They are nevertheless easy to
identify as non-occurring errors because of programmer comments, the most
common of which is “should never happen.” If not “should never happen” then
some variation such as “shouldn't happen”, “can’t happen”, “should never get
here”, “should never occur”, “should never be a problem”, “This shouldn't be
able to happen, of course”, “Not supposed to happen”, “Code should never

ASSERTIONS, EXCEPTIONS, AND LOGGING 713


reach here”, “never reached”, “impossible”, etc. (all of which can be found in the
core API). Still other throw statements are documented as being either an
“assertion” or “internal error”, both of which are programmer speak for “should
never happen.” These are all sure signs of a non-occurring error.
Non-occurring errors are exempted from compile-time checking for essen-
tially the same reason as runtime exceptions in that they are not thrown. Runt-
ime exceptions are not thrown in production. Non-occurring errors are not thrown
at all. Note, however, that these are the only errors that “never occur.” Every
method errors and certainly recurring end-user errors occur for predictable
reasons. For example, it would be absurd to suggest that an OutOfMemory-
Error “never occurs.” The following explanation as to why an Assertion-
Error is an error instead of an exception is interesting in this regard.
Why is AssertionError a subclass of Error rather than
RuntimeException?
This issue was controversial. The expert group discussed it at length,
and came to the conclusion that Error was more appropriate to dis-
courage programmers from attempting to recover from assertion fail-
ures. It is, in general, difficult or impossible to localize the source of an
assertion failure. Such a failure indicates that the program is operating
“outside of known space,” and attempts to continue execution are likely
to be harmful. Further, convention dictates that methods specify most
runtime exceptions they may throw (via "@throws" doc comments).
It makes little sense to include in a method's specification the circum-
stances under which it may generate an assertion failure. Such informa-
tion may be regarded as an implementation detail, which can change
from implementation to implementation and release to release.45

AssertionError is the definitive non-occurring error. The expectation that


these errors are not thrown is such that assertions are disabled by default. On
the other hand, assertions are used during development and unit testing as a
debugging tool. This explains why this issue would have been so controversial in
an expert group.

45. Unascribed, “Programming with Assertions” in the API docs for the 1.4 release, “Design FAQ -
The AssertionError Class.”

714 JAVA RULES


The third kind of error is a recurring end-user error. This is not a very
descriptive name in that it does not indicate that these are unrecoverable
errors thrown during system initialization, both of which are defining charac-
teristics of recurring end-user errors. Instead the name emphasizes that these
errors are recurring and that they are caused by the end user. I emphasize
“recurring” because these errors should be declared as SystemConfig-
urationError subclasses. As such, they are the only system configuration
errors that are expected to occur on a routine basis. The fact that these
errors are “recurring” is what makes them a fundamentally different
kind of error. They are not every method errors, and they are definitely not non-
occurring errors. The name also emphasizes the fact that they are caused by
something the end user is doing wrong. That is because all recurring end-user
errors should be documented using the @throws tag, but only so that top-level
exception handlers can catch the exception and explain to the end user what it is
that they are doing wrong. (The documentation should explicitly mention top-level
exception handlers.)
There are only two examples of recurring end-user errors of which I am
aware. Neither are part of the core API, but perhaps should be.46 The first is an
AssertionsDisabledError, which is discussed at the very bottom of 6.2
Assertions. This error is thrown by a class that cannot be loaded unless asser-
tions are enabled. Because assertions are disabled by default, it is predicable
that this error is going to be thrown. The other example of a recurring end user
error is HeadlessException (an error regardless of how it was declared). If
the DISPLAY environment variable is not properly set on a UNIX machine, initial-
ization of either the sun.awt.motif.MToolkit or sun.awt.X11-
GraphicsEnvironment classes used to throw an InternalError that
was one of the worst InternalError fiascoes ever. Had this Internal-
Error been recognized as a recurring end-user error and so documented, a
great deal of the frustration directed at the software engineers at Sun could
have been diverted to the actual problem of coping with the fact that headless

46. UnsupportedClassVersionError added to the java.lang package in the 1.2


release comes close, but strains the definition of end-user, is arguably not a recurring error, and
definitely should not be documented using the @throws tag.

ASSERTIONS, EXCEPTIONS, AND LOGGING 715


AWT was not yet a reality. Interestingly, now that headless AWT is a reality, the
“AWT Enhancements in the Java 2 SDK, v1.4” document states:
Source code should check for headless, so that the exception may be
caught gracefully. For example, see the following pre-headless imple-
mentation of the class Foo:

class Foo {
static Choice c = new Choice(); // could throw
HeadlessException
}

The new and improved implementation of Foo should be placed in a


static block:
class Foo {
static Choice c;
static {
try {
c = new Choice();
catch (HeadlessException e) {

}
}
}
[END OF QUOTE]47

I don’t think so. Any time you see “gracefully” think top-level exception han-
dler. HeadlessException is likely going to be caught only by top-level
exception handlers which can then explain the problem to end users in the sim-
plest possible terms. This is what Bug Id 4510992 (an RFE) was aiming for, but
it missed the mark by focusing only on getting rid of the stack trace that was
being printed. One thoughtful JDC member included the following comment at
the bottom of that bug report.
This is a problem for customers. We would like to give them a friendly
error message when this occurs since the stacktrace is only useful to
developers (and only to Sun developers at that since it is in your code).
Because the stacktrace happens in your code and the exception is not

47. See java.sun.com/j2se/1.4/docs/guide/awt/AWTChanges.html#headless.

716 JAVA RULES


re-thrown we can't catch it and print out “You must set your DISPLAY
environment variable” or some other more meaningful message. This
should be fixed!48

Such a “meaningful message” is precisely what a top-level exception handler


would display.
While I am on the subject, why is HeadlessException a runtime excep-
tion instead of an error (as it always was prior to the headless AWT changes in
the 1.4 release)? The rationale for this decision is explained as follows.
A new public exception class, java.awt.HeadlessException,
is introduced. It is derived from UnsupportedOperation-
Exception, which derives from RuntimeException, so that
existing implementations of methods that throw the new exception will
not require signature changes.49

I fear this is an example of the classic object-oriented design problem of feeling


a compulsion to extend existing classes. How else does one explain the rather
drastic change from InternalError to a runtime exception? More impor-
tantly, how does one recover from being decapitated? HeadlessException
should have been declared as a recurring end-user error (by which I mean an
SystemConfigurationError subclass that is always documented using
the @throws tag). It is clearly not a runtime (or debugging) exception because
it depends on the setting of the DISPLAY environment variable, over which a
programmer has no control. Here is yet further proof that the differences
between errors, runtime exceptions, and all other (checked) exceptions is not
generally understood.

48. See comments at the bottom of Bug Id 4510992.


49. See java.sun.com/j2se/1.4/docs/guide/awt/AWTChanges.html#headless.

ASSERTIONS, EXCEPTIONS, AND LOGGING 717


That LinkageError and VirtualMachineError are the only every
methods errors there will ever be in the Java programming language has very
significant implications because recurring end-user errors are very rare.

This means most if not all of the errors an application programmer


(or a core API programmer for that matter) will ever throw are non-
occurring errors that “should never happen.”

And by extension it also means that the only errors that will ever be added to the
Java programming language are non-occurring errors such as UnknownByte-
OrderError, which is discussed in the following subsection. Understand this
and the definition of runtime exceptions above, and you will never again have to
doubt your understanding of the Throwable class hierarchy.
All of the errors in the core API are either every method errors or non-occur-
ring errors except two. Those are AWTError and the much more recent addi-
tion of CoderMalfunctionError in the java.nio.charset package.
If my analysis of errors is comprehensive and sound, there should be some ratio-
nal explanation as to why these are not every method, non-occurring, or recur-
ring end-user errors. I believe there is.
AWTError is not a general exception class such as LinkageError or
VirtualMachineError. AWTError is the only error class in the
java.awt package. It is used as follows.
• As a SystemConfigurationError if core API or support classes
(such as Toolkit implementations) cannot be “found or instantiated.” Sys-
tem configuration errors are discussed in the following subsection
• As an IllegalArgumentException
• As an IllegatStateException
• As an UnsupportedOperationException
This list looks a lot like the lists in the following subsection on unspecified runt-
ime exceptions and errors. That is in fact how AWTError is being used. Each
use must be considered a different subsystem specific unspecified runtime

718 JAVA RULES


exception or error. As with all unspecified runtime exceptions and errors, the
API docs for the AWTError class do not describe any particular error.
Thrown when a serious Abstract Window Toolkit error has occurred.50

The historical explanation for subsystem specific unspecified runtime exceptions


and errors is the same as it is for unspecified runtime exceptions and errors in
general. For example, UnsupportedOperationException was not
added to the language until the 1.2 release. One of the most important historical
explanations for unspecified runtime exceptions and errors is the lack of a stan-
dard exception class for system configuration errors. This was precisely how
AWTError was first used in the 1.0 release (in the getDefaultToolkit()
method of java.awt.Toolkit):

if (toolkit == null) {
String nm = System.getProperty("awt.toolkit",
"sun.awt.motif.MToolkit");
try {
toolkit = (Toolkit)Class.forName(nm).newInstance();
} catch (ClassNotFoundException e) {
throw new AWTError("Toolkit not found: " + nm);
} catch (InstantiationException e) {
throw new AWTError("Could not instantiate Toolkit: " + nm);
} catch (IllegalAccessException e) {
throw new AWTError("Could not access Toolkit: " + nm);
}

This try block from the java.awt.Toolkit class loads the default toolkit.
The problem is that all three of these checked exceptions represent the
same system configuration error. Now that exception chaining is available,
such try statements should be coded as follows.

if (toolkit == null) {
String name = System.getProperty("awt.toolkit",
"sun.awt.motif.MToolkit");
try {
toolkit = (Toolkit)Class.forName(name).newInstance();

50. API docs for the java.awt.Error class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 719


} catch (Throwable e) {
throw new SystemConfigurationError("Cannot instantiate " +
name, e);
}

This much more concise code throws a SystemConfigurationError


instead of an unspecified one.
There are two reasons why AWTError (or more generally the use of a sin-
gle exception class for all of the errors in an entire package or subsystem)
should be considered an anomaly:
• It seriously distorts the documentation
• Converting runtime exceptions into errors makes no sense
Instead of documenting the familiar runtime exceptions (and the not so familiar
SystemConfigurationError ) listed above, the java.awt package doc-
uments that the much more “serious” AWTError is thrown (which much like
InternalError suffers from the Chicken Little “the sky is falling” problem).
This AWTError is unfamiliar and only serves to intimidate unsuspecting client
programmers.
The other reason why AWTError should be regarded as an anomaly is the
same reason why CoderMalfunctionError should never have been
declared. Look again at the list of AWTError uses above. It includes the runt-
ime exceptions IllegalArgumentException , IllegalStateExcep-
tion, and UnsupportedOperationException. When used as one of
these an AWTError essentially converts (or if you prefer, translates) a runtime
exception into an error. This is a very disturbing trend in the core API. As
stated above, runtime exceptions should never be caught. Doing so is always a
sure sign that something is fundamentally wrong. Here is an example from the
core API that explicitly converts a runtime exception into an error:

try {
for (int i = 0; i < len; i++) {
int c = str.charAt(i) & 0xFFFF;
if (c >= 0x0001 && c <= 0x007F) {
res[utf8Idx++] = (byte) c;
} else if (c == 0x0000 ||

720 JAVA RULES


(c >= 0x0080 && c <= 0x07FF)) {
res[utf8Idx++] = (byte) (0xC0 + (c >> 6));
res[utf8Idx++] = (byte) (0x80 + (c & 0x3F));
} else {
res[utf8Idx++] = (byte) (0xE0 + (c >> 12));
res[utf8Idx++] = (byte) (0x80 + ((c >> 6) & 0x3F));
res[utf8Idx++] = (byte) (0x80 + (c & 0x3F));
}
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new InternalError
("Bug in sun.reflect bootstrap UTF-8 encoder");
}

Examples such as these are not very common, but that does not make them any
less disturbing. Here is another one from sun.misc.Cache:

public Cache () {
try {
init(101, 0.75f);
} catch (IllegalArgumentException ex) {
// This should never happen
throw new Error("panic");
}
}

private void init(int initialCapacity, float loadFactor) {


if ((initialCapacity <= 0) || (loadFactor <= 0.0)) {
throw new IllegalArgumentException();
}

This is really messed up. Allowing for the fact that this code predates assertions,
it should have been coded as follows.

public Cache () {
init(101, 0.75f);
}

private void init(int initialCapacity, float loadFactor) {


assert ((initialCapacity <= 0) && (loadFactor <= 0.0));

ASSERTIONS, EXCEPTIONS, AND LOGGING 721


The only use of CoderMalfunctionError of which I am aware does the
same thing:

CoderResult cr;
try
cr = $code$Loop(in, out);
} catch (BufferUnderflowException x) {
throw new CoderMalfunctionError(x);
} catch (BufferOverflowException x) {
throw new CoderMalfunctionError(x);
}

BufferUnderflowException and BufferOverflowException are


both runtime exceptions. (For those of you who are not aware, many of the
source code files in the java.nio package a preprocessed, which explains
the $code$Loop method name.)
The notion that runtime exceptions need help unwinding a thread is predi-
cated on the mistaken belief that errors are not caught. This was the concept
behind the original design of the Throwable class hierarchy: exceptions are
caught, unrecoverable errors are not. As stated in the JLS:
The class Error is a separate subclass of Throwable , distinct
from Exception in the class hierarchy, to allow programs to use the
idiom:

} catch (Exception e) {

to catch all exceptions from which recovery may be possible without


catching errors from which recovery is typically not possible.51

And as stated in the API docs for the Error class:


An Error is a subclass of Throwable that indicates serious prob-
lems that a reasonable application should not try to catch.52

However, this specification and the API docs for the Error class are seriously
outdated. Since the demise of ThreadDeath, the catchall exception handler
of choice is catch(Throwable e). This is as it should be. Catching a bug or

51. Gosling et al., §11.5 “The Exception Hierarchy.”


52. API docs for the java.lang.Error class.

722 JAVA RULES


programmer error that is only thrown during development and unit testing
makes no more sense than catching a non-occurring error. Both should only be
caught when the intent is to define an exception handler designed to catch all

Neither runtime exceptions nor errors are explicitly caught. There-


fore converting a runtime exception into an error makes no sense.

exceptions and errors, as if to say “if anything goes wrong.” This is the subject
of 6.8.1.1 Catchall Exception Handlers. Nowadays expressing “if anything goes
wrong” as catch(Throwable e) has become a necessity precisely because
of the deterioration of the programmer conventions that differentiate errors from
runtime exceptions. Consequently, an error is no less likely to be caught
than a runtime exception. This is why converting runtime exceptions into
errors makes no sense.

NOTE 6.3
The checked exceptions in the throws clause of a method or con-
structor header are either explicitly thrown or propagated. The
former means that the throw statement can be found in the method
or constructor body. The latter implies that the evaluation of a method
invocation or class instance creation expression resulted in an excep-
tion being thrown. This is sometimes described as “implicitly” throwing
an exception. I do not use that terminology, however, because to me it
implies that the exception is always thrown which, of course, is not
the case. The distinction between explicitly throwing versus propagat-
ing an exception is important in the definition of complex exceptions in
the following section.

6.5.1 General Exception Classes


In order to discuss general exception classes, you must think in terms of throw
statements. Every throw statement could potentially require the declaration

ASSERTIONS, EXCEPTIONS, AND LOGGING 723


of a new exception class. Exception classes rarely map to a single throw state-
ment, however. CloneNotSupportedException is an example of what
might be described as a simple exception. All instances of this exception map
to the same throw statement in the native clone() method of the
Object class. The overwhelming majority of exception classes are complex
exceptions that map to more than one throw statement. There are three ways
in which to “map” to a throw statement:
• An exception maps directly to a throw statement if it is the name of the
exception class thrown
• An exception indirectly maps to a throw statement if exception translation
is involved. For example,
try {

throw new LowLevelException();

} catch(LowLevelException e) {
throw new HighLevelException("detail message", e);
}

In this example, HighLevelException is said to indirectly map to the


throw statement in bold. In other words, the throw statement that trans-
lated the low-level exception into a high-level exception is not thought of as a
“leaf node” in this conceptual throw statement tree.
• General exception classes map to all of the same throw statements to
which their subclasses map
The point of this exercise is to show that general exception classes are not the
only kind of complex exception that maps to more than one throw statement. In
fact, there are three substantially different kinds of complex exception classes:
• Common Exception Classes: Most of the runtime exceptions and errors in
the java.lang package are common exception classes that are
explicitly thrown in many different packages. Few if any checked exceptions
are explicitly thrown in many different packages. They may be declared to
be thrown, but that is because they are propagated when a method invoca-
tion or class instance creation expression is evaluated. Common exceptions
are substantially different from high-level exceptions in that they are usually

724 JAVA RULES


direct subclasses of RuntimeException or Error and do not have
subclasses.
• General Exception Classes: A general exception class is loosely defined
as a superclass in the Throwable class hierarchy. However, there are at
least three fundamentally different kinds of general exception classes:
° Nonfunctional Groups: There are only two examples of
nonfunctional groups in the core API of which I am aware;
LinkageError and VirtualMachineError serve
no other purpose than to group related errors in the
Throwable class hierarchy. They are not caught. (Pro-
grammers will always find a reason to catch an exception.
There are a handful of classes in the core API that catch
LinkageError, but their number is so few that they can
be overlooked.) Nonfunctional groups are best thought of as
documenting the close relationship between their sub-
classes.
° High-Level Exceptions: There are three defining charac-
teristics of high-level exceptions: they are always checked
exceptions; they almost always have subclasses (though
there are exceptions to this rule, such as JarException);
and there is usually only one per package. In addition,
high-level exceptions are often thrown as the result of excep-
tion translation, but this by no means is a defining character-
istic. Exception translation is rarely if ever the only
reason a high-level exception is thrown. The API docs
for high-level exceptions are notably different than low-level
exceptions in that they describe general failures, and not
specific reason(s) why the exception is thrown. For example,
SocketException is “an error in the underlying proto-
col, such as a TCP error,”53 RemoteException repre-
sents “communication-related exceptions that may occur
during the execution of a remote method call,”54 and
SQLException is “a database access error.”55 These
checked exceptions are every bit as “serious” as an error in
that they represent the failure of an entire package. In fact,
in the context in which they are thrown they can be defined
as unrecoverable checked exceptions.

53. API docs for the java.net.SocketException class.


54. API docs for the java.rmi.RemoteException class.
55. API docs for the java.sql.SQLException class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 725


° Umbrella Exceptions: This is a term of my own making;
one that I think is not only useful, but also very descriptive.
Umbrella exceptions are a special kind of high-level excep-
tion. The defining characteristic of an umbrella exception is
that it is the superclass of not just some, but all of the
checked exceptions in a package. IOException is what
can only be described as the “mother of all umbrella excep-
tions.” It maps to hundreds if not thousands of throw state-
ments in no less than nine packages of the core API.
Umbrella exceptions are discussed at length in the following
subsection.
To this list could be added “subsystem specific unspecified exceptions and
errors” (that’s a mouthful) such as AWTException and AWTError. (See
the discussion at the bottom of the following subsection on umbrella excep-
tions.)
• Low-Level Exceptions: Inasmuch as simple exceptions that map to a sin-
gle throw statement are actually quite rare, I define all remaining complex
exception classes as low-level exceptions. In fact, one of the defining
characteristics of low-level exceptions is that their API docs tend to list all of
the conditions under which the exception is thrown. Each condition repre-
sents a separate throw statement (or perhaps multiple throw state-
ments). This is markedly different from high-level exceptions which tend to
just describe a very general failure.
The terminology of exception translation in which a low-level exception “causes”
a high-level one completely overlooks the reality of complex exceptions that map
directly (versus indirectly) to two or more throw statements. Low-level excep-
tions usually map (directly) to at least two throw statements, each of which
may be said to “cause” the exception. For example, the API docs for the
OptionalDataException class in the java.io package read as follows.
Exception indicating the failure of an object read operation due to
unread primitive data, or the end of data belonging to a serialized
object in the stream. This exception may be thrown in two cases:

• An attempt was made to read an object when the next ele-


ment in the stream is primitive data. In this case, the Option-
alDataException's length field is set to the number of bytes

726 JAVA RULES


of primitive data immediately readable from the stream, and
the eof field is set to false.
• An attempt was made to read past the end of data consum-
able by a class-defined readObject or readExternal method.
In this case, the OptionalDataException's eof field is set to
true, and the length field is set to 0.56
There are also examples of “low-level” exceptions that involve exception transla-
tion. For example, here are the API docs for UnmarshalException in the
java.rmi package:
An UnmarshalException can be thrown while unmarshalling the
parameters or results of a remote method call if any of the following
conditions occur:

• if an exception occurs while unmarshalling the call header


• if the protocol for the return value is invalid
• if a java.io.IOException occurs unmarshalling para-
meters (on the server side) or the return value (on the client
side).
• if a java.lang.ClassNotFoundException occurs
during unmarshalling parameters or return values
• if no skeleton can be loaded on the server-side; note that
skeletons are required in the 1.1 stub protocol, but not in the
1.2 stub protocol.
• if the method hash is invalid (i.e., missing method).
• if there is a failure to create a remote reference object for a
remote object's stub when it is unmarshalled.57
As you can see, two of the conditions under which this low-level exception are
thrown involve exception translation. Thus UnmarshalException is a “low-
level exception” that maps both directly and indirectly to a number of different
throw statements. It is too specific to be considered a high-level exception.
One should be very careful when naming complex exceptions that map to
more than one throw statement. FileNotFoundException is the classic
example of this. Here are the conditions under which it is thrown:

56. API docs for the java.io.OptionalDataException class.


57. API docs for the java.rmi.UnmarshalException class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 727


Signals that an attempt to open the file denoted by a specified path-
name has failed.

This exception will be thrown by the FileInputStream, File-


OutputStream, and RandomAccessFile constructors when a
file with the specified pathname does not exist. It will also be thrown by
these constructors if the file does exist but for some reason is inacces-
sible, for example when an attempt is made to open a read-only file for
writing.58

The FileNotFoundException exception class name in no way indicates


that the exception is also thrown if the file is found but “for some reason is inac-
cessible.” Read-only files are not the only reason why attempting to open a file
will throw a misleading FileNotFoundException. Win32 does not allow
files that are already opened for reading to be opened for writing. Attempting to
open a directory will also throw a FileNotFoundException . Basically if for
any reason a file cannot be opened a FileNotFoundException is thrown.
Bug Id 1267237 which significantly was submitted very early on September 9,
1996 offers some insight into what might have led to this predicament:
The JLS sez [sic] “If the file could not be opened”, it throws a FileNot-
FoundException. This follows the protection scheme of many lan-
guages in which “if you can't open it, you have no right to even know it
exists.”

Better detailed messages were supposedly added as a result of Bug Id


4140910, but I still get a “C:\Java\classes (Access is denied)”
detail message when trying to open a directory. Bug Id 4079140, “java.io.IOEx-
ception: Add subclasses to better describe failure” is an “In progress, request
for enhancement” that I hope is never implemented. Subclassing FileNot-
FoundException would be an object-oriented design mistake. The simple
fact of the matter is that this complex exception was misnamed. It should be a
CannotOpenFileException with subclasses.

58. API docs for the java.io.FileNotFoundException class.

728 JAVA RULES


6.5.1.1 Umbrella Exceptions
IOException is an examle of an umbrella exception. It is important to under-
stand the motivation behind umbrella exceptions. For example, an article origi-
nally published in JavaWorld and available on the Java Web site includes the
following.
Instead of adding a new exception class to a method's throws
clause, try to group related exception types into an object hierarchy
and include only the parent exception type in the throws clause. The
IOException class from the java.io package provides a good
example. More specific exception classes, such as EOFException,
are defined as subclasses of IOException, but nearly all the meth-
ods in the java.io package are defined as throwing only
IOException.
Subclassing exceptions from a common parent allows exception han-
dling to proceed in a more object-oriented manner. Callers can choose
to treat all IO-related exceptions equally with a single catch block by
simply catching IOException. But if callers can deal with a specific
type of IO-related exception in a specific manner, they can catch the
more specific exception type first and recover appropriately.

Grouping exception types particular to a package or application


together by subclassing them from a common parent improves the sta-
bility of methods' throws clauses. You can add specific exception
types to the package in future versions without affecting existing meth-
ods' signatures, which in turn means that you don't need to change cli-
ent code every time you add a new exception subclass.59

Compare this to the following diametrically opposed paragraph from The Java
Programming Language.
A method can throw several different classes of checked exceptions—
all of them extensions of a particular exception class—and declare only
the superclass in the throws clause. By doing so, however, you hide

59. Brian Goetz, “Exceptional Practices, Part 1: Use Exceptions Effectively in Your Programs
By” available at developer.java.sun.com/developer/technicalArticles/
Programming/exceptions/ (Reprinted from JavaWorld August 2001). Note that this article
incorrectly states that the throws clause is part of the method signature.

ASSERTIONS, EXCEPTIONS, AND LOGGING 729


potentially useful information from programmers invoking the method,
because they don’t know which of the possible extended exception
types could be thrown. For documentation purposes, the throws
clause should be as complete and specific as possible. 60

Who is right? Before answering that question, consider the following paragraph
from the very next page in The Java Programming Language.
You should be explicit in your throws clause, listing all of the exceptions
you know that you throw, even when you could encompass several
exceptions under some superclass they all share. This is good self-doc-
umentation.60

So far so good. This is entirely consistent with what was said on the previous
page. However, the same paragraph continues as follows.
Deciding how explicit you should be requires some thought.60

Huh? Are there different levels of explicitness? Now watch as The Java Pro-
gramming Language uses the same example of the java.io package as
was used in the above quote from the JavaWorld article.
If you are designing a general interface or superclass you have to think
about how restrictive you want to be to the implementing classes. It
may be quite reasonable to define a general exception you use in the
interfaces’s throws clause, and expect that the implementing classes
will be more specific where possible. This tactic is used by the
java.io package, which defines a general IOException type for
its methods to throw. This lets implementing classes throw exceptions
specific to whatever kind of I/O is being done. For example the classes
that do I/O across network channels can throw various network-related
subclasses of IOException, while those dealing with files throw file-
related subclasses.60

The JavaWorld article is wrong because it generalizes from an example of an


umbrella exception, but that does not make The Java Programming Lan-
guage right. The IOException example is confusing because it contradicts

60. Ken Arnold, James Gosling, and David Holmes, The Java Programming Language, Third Edi-
tion (Boston, Addison-Wesley Professional, 2000), §8.3, “The throws Clause.”

730 JAVA RULES


everything said up to that point. In particular, the definition of “general interface
or superclass” is too vague. The real problem, however, in both the JavaWorld
article and corresponding paragraphs in The Java Programming Language is
a failure to recognize the uniqueness of umbrella exceptions as defined in this
section.
Umbrella exceptions are necessary because of a combination of the com-
piler-enforced method contract and the need for high-level exceptions in the pack-
ages that subclass large frameworks. Note that this says large frameworks. It is
tempting to make the blanket statement that they are used in all frameworks, but
the Collections “Framework” did not require the use of an umbrella exception. The
larger the framework, the more likely that subclasses in other packages will have
their own high-level exception. There is one umbrella exception for each pack-
age in which they are used. It is by definition the superclass of all other checked
exceptions in the package. Not some, all. IOException is the most impor-
tant example of an umbrella exception in the core API. Others include General-
SecurityException, and NamingException. These are the umbrella
exceptions for the java.io, java.security, and javax.naming pack-
ages, respectively.
Smaller packages such as java.sql may have a checked exception that
is the superclass of all other checked exceptions in the package, but that does
not make it an umbrella exception. The java.sql package is not unlike other
small packages such as java.util.jar and javax.print in which there
is only one checked exception. It just so happens that SQLException has a
few subclasses; so does ZipException in the java.util.zip package.
None of these are umbrella exceptions, however. Umbrella exceptions are sim-
ply not used in smaller packages.
The necessity for umbrella examples is that subclasses cannot throw an
exception that is not assignment compatible with one of the exceptions thrown in
the overridden method. Unless umbrella exceptions are used, this require-
ment precludes the use of a high-level exception in subclasses. To explain
this in detail, I will use IOException—the “mother of all umbrella excep-
tions”—as an example. As with all umbrella exceptions, IOException is the
the superclass of all checked exceptions in the java.io package. However,

ASSERTIONS, EXCEPTIONS, AND LOGGING 731


what really makes IOException an “umbrella” exception is that the
designers of the java.io package were careful to always name
IOException, and only IOException, in the throws clause of acces-
sible methods. This allows subclasses to catch any exception thrown when
invoking those methods and translate it into a high-level exception such as
SocketException that does not even exist in the java.io package. The
only catch is that SocketException has to be a subclass of the umbrella
exception (in this case, IOException).
The designers of the java.io package were probably the first Java pro-
grammers to confront the necessity of an umbrella exception, so I can under-
stand why they may have thought it was a good idea to explicitly throw IO-
Exception . That was a mistake, however. Umbrella exceptions should not
be explicitly thrown. They should only be used in throws clauses. In fact,
the package-level API docs for packages that use umbrella exceptions ideally
should include a comment such as the following:
IOException is an “umbrella exception” the sole purpose of which
is to make it possible for subclasses in other packages to declare their
own high-level exceptions (that correspond to FileException in
this package). Umbrella exceptions are never thrown; they are
only used in throws clauses. Therefore they should never be docu-
mented using the @throws tag. What every class in this package
does document using the @throws tag are the checked exceptions
actually thrown.

As indicated in these make believe comments from the API docs the java.io
package, FileException is the logical high-level exception for that package,
not IOException. Most of the methods that explicitly throw IOException
should have thrown FileException instead. Note that in a package such as
java.io that uses an umbrella exception, the only documentation of the fact
that a high-level exception such as our imaginary FileException is thrown is
in the @throws comments.
The problems in java.io are even worse because the read and write prim-
itives (native methods that actually do the work of reading from or writing to
files) even throw IOException. The read and write primitives should throw

732 JAVA RULES


FileIOException, which would be consistent with the FileNotFound-
Exception thrown by constructors in the FileInputStream class. In
other words, IOException is at once a low-level, high-level, and umbrella
exception. Nothing could be more confusing.
What about the argument that Goetz makes that “if callers can deal with a
specific type of IO-related exception in a specific manner, they can catch the
more specific exception type first and recover appropriately.”59 Isn’t this a valid
argument in favour of using exception superclasses that are not umbrella excep-
tions? The answer is Yes, and that is precisely why the definition of high-level
exceptions in the previous section includes both subclasses and exception trans-
lation. The problem with his analysis is that it may encourage a coding style in
which exception classes are improperly subclassed in a wayward effort to mimic
the IOException umbrella exception. The result would be as disastrous as
copying AWTException. This is why it is so important to fully understand the
motivation behind umbrella exceptions.
High-level exceptions are a combination of subclassing and exception trans-
lation. FileException and FileNotFoundException is an example of
appropriate subclassing. Both of these exception classes are declared in the
java.io package (assuming FileException actually existed). When sub-
classing a high-level exception, that is usually the case, whereas the raison
d'être for umbrella exceptions is so that they can be subclassed by high-level
exceptions in other packages. In order to make this possible the umbrella excep-
tion must be a superclass of all of the checked exceptions in the package in
which it is used. Can you see how radically different are these motivations? To
suggest that IOException is an example of a high-level exception is
very misleading because the cause of many (if not most) high-level
exceptions is a low-level exception thrown in a different package. In that
case, exception translation (not subclassing) is required. Client programmers
cannot catch low-level exceptions that are the result of exception translation, but
as of the 1.4 release they can query the cause of a high-level exception by invok-
ing the getCause() method in Throwable.
It is important to understand why umbrella exceptions are the superclass of
all checked exceptions. If they are to be the only exception type in the throws

ASSERTIONS, EXCEPTIONS, AND LOGGING 733


clause of an overridable method, then all checked exceptions must be assign-
able to them. Do not get carried away with umbrella exceptions, however. They
are the superclass of all checked exceptions. That does not include runtime
exceptions. The difference between an umbrella exception and some-
thing like AWTException is subtle, but very significant. For example,

public synchronized void reset() throws IOException {


throw new IOException("mark/reset not supported");
}

There is no reason for this implementation to throw a checked exception. The


throws clause is okay. The problem is that the dummy implementation should
throw an UnsupportedOperationException . Even worse would be to
actually declare all of the runtime exceptions in a package as checked excep-
tions merely to make them subclasses of an umbrella exceptions. Not having
used the javax.naming package, I am nevertheless very suspicious that the
javax.naming package has done this with NamingException because
there are no runtime exceptions in the package. How can that be? No Bugs? No
programmer errors in a major package? Look at the java.nio packages.
They have plenty runtime exceptions.
AWTException is not an umbrella exception for a number of reasons. For
one, the design of the java.awt package in the 1.0 release was that it only
had two exceptions classes: AWTException and AWTError. This is signifi-
cantly different from umbrella exceptions in that AWTException has no sub-

AWTException is a fundamentally flawed design because catch-


all exception handlers are the prerogative of client program-
mers using either catch(Exception e) or the more up-to-date
catch(Throwable e) exception handler.

classes. The motivation for AWTException is not to make it possible for


subclasses to throw a different high-level exception. The motivation for such a
class is obviously to make it possible to catch a single exception. One might
think of these as “catchall exception classes” as opposed to catchall exception

734 JAVA RULES


handlers. The problem with this idea is that catchall exception handlers are the
prerogative of client programmers, not the programmers who design and imple-
ment classes.
Rather than being a high-level exception, AWTException is a rare exam-
ple of a subsystem specific unspecified checked exception. By that I mean
throwing AWTException is not substantially different from throwing
Exception, only it is limited to the java.awt package. Like all unspecified
exceptions, the API docs for AWTException do not describe any specific
exception:
Signals that an Absract (sic) Window Toolkit exception has occurred.61

The argument against the use of unspecified checked exceptions is not substan-
tially different from the argument against the use of unspecified runtime excep-
tions and errors. See 6.5.2 Unspecified Runtime Exceptions and Errors for a
discussion.
Finally, there is the question of documenting the exception specification in a
package that uses an umbrella exception. The important point to realize here is
that this is nothing like selectively documenting the low-level exceptions thrown
in a method or constructor that is declared to throw a high-level exception. All of
the checked exceptions thrown in the body of a method or constructor
that is declared to throw an umbrella exception must be documented
using the @throws tag, without exception (pun unavoidable). This is a critical
point. The umbrella exception has effectively preempted the normal use of the
throws clause, which is to document all of the checked exceptions thrown.
Using the @throws tag to document those checked exceptions is therefore not
an option.

NOTE 6.4
The subject of unspecified checked exceptions, which is defined as
the throwing of Exception, is not discussed in the following section.

61. API docs for the java.awt.AWTException class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 735


Unspecified checked exceptions are rare. None of the examples I was
able to find are part of the core API proper. They were all in either the
sun or com.sun packages.

6.5.2 Unspecified Runtime Exceptions and Errors


This is virgin territory. Unspecified runtime exceptions and errors are used
extensively in the core API as well as in the sun and com.sun support pack-
ages. It is therefore necessary for application programmers to be aware of
them, especially because many of them are runtime exceptions thrown during
development and unit testing. Some individual classes such as Object-
StreamClass in the java.io package (which as of this writing throws
InternalError no less than 18 times) use them extensively. An unspeci-
fied runtime exception is defined as throwing RuntimeException. An
unspecified error is defined as throwing either Error or InternalError.
There is a subtle difference here. Merely not documenting that a runtime
exception or error is thrown (using the @throws tag) means the exception is
undocumented. Actually throwing RuntimeException or Error (versus a
subclass) is unspecified (and rarely documented) because the API docs for these
exception superclasses do not describe any particular runtime exception or
error. For example,
RuntimeException is the superclass of those exceptions that can
be thrown during the normal operation of the Java Virtual Machine.

A method is not required to declare in its throws clause any sub-


classes of RuntimeException that might be thrown during the
execution of the method but not caught.62

These are the entire API docs for the RuntimeException class. As you can
see, no particular runtime exception is described. Thus throwing Runtime-
Exception is defined as using an unspecified runtime exception. The API

62. API docs for the java.lang.RuntimeException class. I am fully aware of the fact that I
repeat these API docs a number of times in this chapter. This is done for the reader’s convenience.

736 JAVA RULES


docs for the Error class (repeated here for your convenience) are no different
in this regard:
An Error is a subclass of Throwable that indicates serious prob-
lems that a reasonable application should not try to catch. Most such
errors are abnormal conditions. The ThreadDeath error, though a
“normal” condition, is also a subclass of Error because most applica-
tions should not try to catch it.

A method is not required to declare in its throws clause any sub-


classes of Error that might be thrown during the execution of the
method but not caught, since these errors are abnormal conditions
that should never occur.63

Again no particular error is described. Thus throwing Error is defined as using


an unspecified error. To this list of unspecified runtime exceptions and errors
must be added the throwing of InternalError. The core API programmers
are using InternalError as an unspecified, “but serious”64 error. In fact,
the subject of unspecified runtime exceptions and errors came to my attention
while studying how InternalError is being used in the core API. The sur-
prising number of bug reports (and forums) that mention InternalError
demonstrate that these errors are not nearly as internal as the responsible pro-
grammers would like to think.
What about the fact that the JLS says “programs can use the pre-existing
exception classes in throw statements…as appropriate.”65 When throwing an
instance of a “pre-existing exception class,” you are bound by the interface con-
tract for that exception class.

Most importantly, the interface contract for an exception always


explains why the exception is thrown. To throw an instance of a “pre-
existing exception class” for some other reason is a violation of the
interface contract for that exception.

63. API docs for the java.lang.Error class.


64. API docs for the java.lang.UnknownError class.
65. Gosling et al., §11.5, “The Exception Hierarchy.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 737


The standard error described in the API docs for InternalError has no cor-
respondence whatsoever with how it is being used in the core API (and increas-
ingly by application programmers in clone() method implementations). This
has resulted in irreparable damage to exception handling on the Java platform.
Specifically, VirtualMachineError has become completely meaningless.
As exception handling on the Java platform has evolved, the catchall exception
handler of choice has become catch(Throwable e). That being the case, it
is easy to see how catch(VirtualMachineError e) could have been
useful. I say “could have been” because this exception handler now catches a
grab bag of unspecified runtime exceptions and errors.
How should InternalError be used? It is noteworthy that both the JLS
and the JVMS include specifications for virtual machine errors. The JLS defers to
the JVMS “for the definitive discussion of these errors.”66 That discussion in the
JVMS begins as follows.
A Java virtual machine implementation throws an object that is an
instance of a subclass of the class VirtualMachineError when
an internal error or resource limitation prevents it from implementing
the semantics of the Java programming language. This specification
defines the following virtual machine errors:

• InternalError : An internal error has occurred in the


Java virtual machine implementation because of a fault in the
software implementing the virtual machine, a fault in the
underlying host system software, or a fault in the hardware.
This error is delivered asynchronously when it is detected
and may occur at any point in a program… 67
Note that this says “because of a fault in the software implementing the virtual
machine.” The question here is what constitutes the Java VM versus the core
API. There are three areas that must be considered:

66. Gosling et al., §11.5.2, “Virtual Machine Errors.”


67. Tim Lindholm and Frank Yellin, §2.16.4, “The Classes Exception and
RuntimeException.”

738 JAVA RULES


• Core API classes
• Support classes in the sun and com.sun packages that ship with prod-
ucts such as the J2SE but are not officially part of the core API
• native methods
The two major components of the Java runtime environment (JRE) are the core
API and the Java VM, so core API classes are clearly not part of the Java VM.
Some support classes in the sun and com.sun packages come tantalizingly
close to being part of the VM. For example, is the Launcher class in the
sun.misc package part of the VM? The answer is no. The application launcher
is a tool used to start or “launch” a Java VM. It is not the part of the Java VM per
se. Although defining what is part of the Java VM is like the ebb and flow of the
tide, the reality is that the programmers responsible for implementing the VM do
not consider any code written in Java to be part of it. They refer to code written
in Java as Java code, a term which is expressly used to distinquish Java code
from “the software implementing the virtual machine.” For example, here is the
evaluation of Bug Id 4131292:
In jdk1.2beta4, the Java level SoftReference implementation has
been completely rewritten and mostly moved into the VM. The Java
code causing the InternalError to be thrown has been removed,
so I cannot test/reproduce the situation.68

Do you see the “ebb and flow of the tide” as the implementation of Java classes
(either core API classes such as SoftReference or support classes in the
sun and com.sun packages) evolve and Java code becomes part of the VM
and vice versa?
It is also important to appreciate the difference between native methods
and the Java VM. A native method that is effectively called from Java code is
not part the Java VM. Some of the worst InternalError abuses are occur-
ring in native methods. This is inexcusable. Just because a native method
is written in C or C++ does not mean that it is part of the JVM. I have looked at
all of the JNU_ThrowInternalError(JNIEnv *env, const char

68. Evaluation of Bug Id 4131292.

ASSERTIONS, EXCEPTIONS, AND LOGGING 739


*msg) function calls in the J2SE and do not see any difference in how
InternalError is being used in native methods versus Java code.69 For
example,

/*
* Class: sun_awt_shell_Win32ShellFolder
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_sun_awt_shell_Win32ShellFolder_initIDs
(JNIEnv* env, jclass cls)
{
if (!initShellProcs()) {
JNU_ThrowInternalError(env,
"Could not initialize shell library");
return;
}

As the comments indicate, this is the implementation of the native init-


IDS() method in the sun.awt.shell.Win32ShellFolder class. The
initShellProcs() function begins as follows.

static BOOL initShellProcs()


{
static HMODULE libShell32 = NULL;
static HMODULE libUser32 = NULL;

// Load libraries
libShell32 = LoadLibrary("shell32.dll");
if (libShell32 == NULL) {
return FALSE;
}
libUser32 = LoadLibrary("user32.dll");
if (libUser32 == NULL) {
return FALSE;
}

69. The initialism JNU stands for JNI Utilities, so JNU_ThrowInternalError is a utility func-
tion for throwing an InternalError in native methods.

740 JAVA RULES


This is a system configuration error, plain and simple. It is thrown if one of the
DLL files that are a part of the host operating system has gone missing.
As discussed below, many uses of InternalError in the core API are as
a substitute for assertions prior to the 1.4 release. This is the same in native
methods. For example,

switch (type) {
case java_awt_geom_PathIterator_SEG_MOVETO:

break;
case java_awt_geom_PathIterator_SEG_LINETO:

break;
case java_awt_geom_PathIterator_SEG_QUADTO:

break;
case java_awt_geom_PathIterator_SEG_CUBICTO:

break;
case java_awt_geom_PathIterator_SEG_CLOSE:

break;
default:
JNU_ThrowInternalError(env, "bad path segment type");
return;
}

This is a logic trap in the native addSegment(int type, float


coords[]) method of sun.java2d.pipe.ShapeSpanIterator. The
case values are convenience constants in the java.awt.geom.Path-
Iterator interface. The difference between this native method and Java
code is hardly perceptible.
These examples are intended to show that throwing InternalError in
native methods is no different than in Java code. This point must be empha-
sized because “getting it right” (which in this case would mean not throwing
InternalError) in native methods requires much more work.
Neither Java code nor native methods are part of the JVM. Essentially
then, the JRE looks like Figure 6.3. Given the interface contract for Internal-
Error in the API docs and specification for virtual machine errors in both the

ASSERTIONS, EXCEPTIONS, AND LOGGING 741


Figure 6.3 The JRE Code Universe (which coincidentally looks a lot like a CD)

JLS and JVMS, it is clear that only a Java VM (as distinct from Java code and
native methods) should throw InternalError. What makes this discus-
sion really interesting is that InternalError and UnknownError were
designed for use in the Classic VM. UnknownError, for example, is a thing
of the past. The design of the HotSpot VM is such that it never throws an
UnknownError. In 6.8.1.1 Catchall Exception Handlers, I return to this sub-
ject in order to differentiate an InternalError from a VM crash.
(InternalError never signifies a VM crash. If it did, the catchall exception
handler catch(Throwable e) would be precarious at best.)
This use (or rather misuse) of InternalError throughout the core API is
very frustrating to application programmers. One JDC member referred to an
InternalError as “this abhorrent message.”70 The frustration can also be
seen in the following description of Bug Id 4171464, “JComboBox should not

70. Comments at the bottom of Bug Id 4517321.

742 JAVA RULES


throw InternalError” (which most likely was written by a software engineer
at Sun because the method in question is package-private):
JComboBox throws InternalError from the mutation methods if
the model is not an instance of MutableComboBoxModel.

InternalError is defined as a VirtualMachineError, and


so is inappropriate for this situation.

java.lang.Error is the most suitable Error for this situation.71

Unfortunately this programmer concludes by saying that a different unspecified


error should be thrown when what he or she if describing is clearly a runtime
exception (an IllegalStateException to be precise) that should be
named. Note that this is only one of a number of bug reports that have been filed
against core API classes for throwing InternalError.72 I chose this one
because of the following comment in the evaluation.
Ah. Good catch. The error certainly is not a VirtualMachineError.73

This is almost comical given the number of classes in the core API that throw
InternalError (in excess of 150 including the sun and com.sun support
packages, which is roughly 2.5% of the total number of class files in the current
J2SE). This surge in the use of unspecified runtime exceptions and errors in the
core API since the 1.0 release is shockingly unprofessional.
InternalError suffers from what can only be described as a Chicken
Little “the sky is falling” problem. As stated above, it is presumably more serious
than an Error, but that is not actually true. I conducted and extensive search
for "new RuntimeException", "new Error", and "new Internal-
Errror" in the J2SE. The very first thing I noticed was that there is no differ-
ence whatsoever in how these unspecified runtime exceptions and errors are
being used. Although I have separated the compiled list of uses below into what
are properly considered to be errors versus runtime exceptions, for any given
use in either of these lists the exception class actually thrown is just as likely to

71. Description of Bug Id 4171464.


72. See Bug Ids 4041997, 4129911, 4171464, 4209579, 4353388, and 4620770 for example.
Other bug reports not listed here object just as strongly to the use of InternalError.
73. Evaluation of Bug Id 4171464

ASSERTIONS, EXCEPTIONS, AND LOGGING 743


be an Error (or InternalError) as it is to be an RuntimeException
and vice versa. To some extent this was to be expected. Prior to the introduction
of assertions in the 1.4 release, one of the most common uses of unspecified
runtime exceptions and errors is in assertion-like if statments and logic traps.
Before the expert group decided on AssertionError (versus a runtime
exception) there was never any agreement as to whether assertions should
throw an error or runtime exception. Other overlapping uses of unspecified runt-
ime exceptions and errors are more troubling. Often unspecified errors are
thrown where it is now common knowledge that a runtime exception such as
IllegalStateException or UnsupportedOperationException
should be used. Here again, the explanation is at least partially historical;
UnsupportedOperationException was not added to the core API until
the 1.2 release.
I should add at this point that part of the problem is that there seems to be a
reluctance on the part of the software engineers at Sun to add general-purpose
exception class for which they are not directly responsible. The prime example
of this is that no one has taken the trouble to add SystemConfiguration-
Error after all these years of desperately needing this error class. Another excep-
tion that comes to mind in this context is AssertionsDisabledError. The
logging utility does not throw this exception, but declaring it would have been consis-
tent with the API docs for that package and a service to users.
The remainder of this section takes a detailed look at how unspecified runt-
ime exceptions and errors are used in the core API followed by an argument
against their continued use either in the core API or elsewhere. As explained in
6.5 The Throwable Class Hierarchy, most errors are either “every method
errors” or “non-occurring errors.” Furthermore, the only every method errors are
LinkageError and VirtualMachineError. Therefore, the only use of
unspecified errors in the core API are the following non-occurring errors:
• Assertions: Most but not all of these uses will eventually be replaced by
actual assertions. Therefore they should be regarded as a pool of would-be
assertions . The throw new Error("Unknown byte order") exam-
ple discussed at length in the remainder of this section is one that will not. It
does not pass the “in complete control” muster for assertions. Still others

744 JAVA RULES


should be replaced with checked exceptions as discussed in the ZipFile
example in 6.5.2.1 The Chicken Little “The Sky is Falling” Problem.
° Regular Assertions: These are if statements with com-
ments such as “assert”, “assert false”, “Assertion botch”,
“should not happen”, “Can't happen”, “Something is terribly
wrong. Shouldn't be here”, “Defensive programming”, “Pro-
grammer's Error!”, and the like. They may also be assertion-
like methods such as assert(boolean assertion)
or assertion(boolean condition, String msg)
° Logic Traps (or control-flow invariants): These are the
default label in a switch statement, the final else in a
nested if-then-else statement, or an unconditional throw
statement at the bottom of a method or constructor. Logic
trap comments usually either begin with or include the
word(s) “unexpected”, “bogus”, “unknown”, “unrecognized”,
“unsupported”, “invalid”, “illegal”, “internal error”, “assert”,
“assert false”, and the like
° Converting Checked Exceptions into Unchecked
Exceptions: There are a lot of these. The implication is that
for one reason or another the checked exception is never
actually thrown. Indeed, the comments for such catch
clauses include words or phrases such as “unexpected
exception”, “internal error”, “can't happen”, “this should
never happen”, “should never occur”, “fatal”, “assertion fail-
ure”, and the like. See 6.9.2 Exception Translation and
Chaining for a detailed discussion of this use of unspecified
runtime exceptions and errors.
• System Configuration Errors: This is a very common use of unspecified
exceptions and errors in the core API. Most of the time an unspecified error
is thrown, either Error or InternalError, but occasionally Run-
timeException is used. The problem here is the lack of a standard
exception class such as SystemConfigurationError in the core
API. Such errors are thrown when a class file that is part of the JRE “cannot
be found or instantiated.”74 Often such classes are loaded using one of the
overloaded ResourceBundle.getBundle methods. In that case,
MissingResourceException should be translated into a System-
ConfigurationError rather than an unspecified error. Instances

74. API docs for the javax.xml.parsers.FactoryConfigurationError and


javax.xml.transform.TransformerFactoryConfigurationError classes.

ASSERTIONS, EXCEPTIONS, AND LOGGING 745


should also be thrown when a properties file that is supposed to be part of
the JRE cannot be loaded, a system property is null, the default character
encoding or a required character encoding such as ISO-8859-1 throws
UnsupportedEncodingException , and the like. In native meth-
ods, missing DLL files are also a (host operating) system configura-
tion error.
The software engineers responsible for the javax.xml package were the first
to recognize that system configuration errors should be declared. They did so
by breaking with a long standing tradition of using unspecified runtime excep-
tions and errors and declaring TransformerFactoryConfiguration-
Error and FactoryConfigurationError instead. This was a bold initia-
tive that should be commended. The only problem I have with their implementa-
tion is that named subclasses should not have been used because these errors
are not documented using the @throws clause and the likelihood that such an
error would actually be thrown is close to nil.75 Furthermore, all of these
errors—regardless of the class or package in which they are thrown—represent
the same error: one or more files that are part of the J2SE (or some other Java
product) have gone missing. This most likely would be caused by some kind of
problem when downloading the product from the Java Web site.
All other uses of unspecified runtime exceptions and errors in the core API
are what most programmers would regard as runtime exceptions:
• Argument Checks: These should be replaced with IllegalArgument-
Exception , IndexOutOfBoundsException, or NullPointer-
Exception
• An if Statement that is clearly an IllegalStateException: There
are a lot of these. More so than any of the other runtime exceptions in this
list, these uses should be converted to IllegalStateException at

75. I briefly contemplated general subclasses such as SystemClassNotFoundError as dis-


tinct from NoClassDefFoundError (which is only documented to be thrown by the “Java Vir-
tual Machine or a ClassLoader instance”), NullSystemPropertyError, Missing-
SystemResourceError as distinct from MissingResourceException (which is a
checked exception), and RequiredCharsetNotFoundError, but even these are overkill.
HostOperatingSystemConfigurationError is especially tempting (except for the fact
that it would be the longest exception class name on record) for DLL files that have gone missing,
but here again, why? These exceptions are just not thrown.

746 JAVA RULES


the earliest possible opportunity. Not only are they setting a really bad
example, but many of them are not documented. The software engineers
responsible for the java.nio package were the first to recognize that
IllegalStateException should be both subclassed and docu-
mented using the @throws tag. For example,
Throws:
IllegalArgumentException - If the preconditions on
the parameters do not hold
NonReadableChannelException - If this channel was
not opened for reading
NonWritableChannelException - If the target channel
was not opened for writing
ClosedChannelException - If either this channel or the
target channel is closed
AsynchronousCloseException - If another thread
closes either channel while the transfer is in progress
ClosedByInterruptException - If another thread
interrupts the current thread while the transfer is in progress,
thereby closing both channels and setting the current thread's
interrupt status
IOException - If some other I/O error occurs
All of these doc comments are from the same method in the File-
Channel class in java.nio.channels. All except the first and last
are instances of IllegalStateException. There are no less than
twelve IllegalStateException subclasses in the java.nio pack-
age (which includes a number of subpackages). Prior to the 1.4 release
there had only been two IllegalStateException subclasses (in the
java.awt package). The java.nio software engineers have set what I
consider to be a very important precedent: IllegalStateException
should be both subclassed and documented using the @throws tag.
• Methods that consist of a single, unconditional throw statement: These
are clearly examples of an UnsupportedOperationException. The
comments usually say the method is “not defined”, “not implemented”, “not
yet implemented”, “not supported”, “not available”, and the like
These lists were compiled by searching for all references to InternalError
in the source code for the J2SE. By “source code” I mean Java code, not

ASSERTIONS, EXCEPTIONS, AND LOGGING 747


native methods. As noted above, however, the use of InternalError in
native methods is not substantially different.
These list are intended to show that nearly all of the uses in Internal-
Error in the core API could be readily replaced by assertions, system configu-
ration errors, and more familiar runtime exceptions. This raises the question of
should IntenralError be used at all? Having spent the better part of two
months studying this problem in detail, I am of the opinion that a “no unspecified
runtime exceptions or errors” policy in the core API (which would necessarily
entail getting rid of the JNU_ThrowInternalError utility function in
jni_util.c) would result in a massive improvement of the Java platform. If
nothing else, it would force core API programmers to think more about why an
exception or error is thrown. That this would be a welcome change is evident in
the number of comments in the core API in which software engineers at Sun
openly question whether the use of an unspecified runtime exception or error is
appropriate.
Another question that must be asked is if changing from unspecified runtime
exceptions and errors to the use of standard exception classes (assuming that
SystemConfigurationError is added to the core API) is a binary com-
patible change? It should be because runtime exceptions are not thrown in pro-
duction, and non-occurring errors are not thrown at all. Changing the few
unspecified runtime exceptions and errors that are documented using the
@throws or @exception tags would have to wait for a feature release to
explain that the use of unspecified runtime exceptions and errors in the core API
is being phased out as a matter of policy (some wishful thinking on my part).
Not all unspecified runtime exceptions and errors can be changed to specific
runtime exceptions and errors. Some may have to be changed to high-level
exceptions. Remember that the assertions are to be regarded as a pool of
would-be assertions. Not all will pass the “in complete control” muster for
assertions. A thoughtful analysis of those that do not would yield some useful
insights into exception handling in the core API. One such insight is what to do
when you are not quite sure what causes an exception to be thrown in the first
place. The ZipFile example discussed in 6.5.2.1 The Chicken Little “The Sky
is Falling” Problem is just such an example. A careful analysis of that example

748 JAVA RULES


reveals that a ZipException should have been thrown. The fact that an
unspecified error was thrown instead means that a binary incompatible change
is now required (the addition of a checked exception as requested in Bug Id
4615343). This example of exception handling in the core API is such an impor-
tant object lesson in why unspecified errors should not be used at all that I devel-
oped an entire section around it.
The remainder of this section is a more general argument against the use of
unspecified runtime exceptions and errors. First it is important to differentiate
between runtime exceptions and errors that are documented using the
@throws clause from those that are not. The difference is the following
javadoc specification.
Note that it is always inappropriate to document that a method throws
an unchecked exception that is tied to the current implementation of
that method. In other words, document exceptions that are indepen-
dent of the underlying implementation…76

As noted above, most unspecified runtime exceptions are not so docu-


mented. However, a handful of classes in the core API use the @throws tag to
document that an unspecified runtime exception or error is thrown. The following
example (which actually uses the @exception tag instead) is from the URL-
Connection class in java.net.

* @exception Error if the factory has already been defined.



public static synchronized void setContentHandlerFactory
(ContentHandlerFactory fac) {
if (factory != null) {
throw new Error("factory already defined");
}

This a classic single use mutator, which is the method equivalent of a blank
final. Such methods should throw a SingleUseMutatorException

76. Unascribed, “How to Write Doc Comments for the Javadoc Tool” at java.sun.com/j2se/
javadoc/writingdoccomments/index.html, (Palo Alto, Sun Microsystems, 2000), “Docu-
menting Exceptions with @throws Tag.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 749


(which is comparable to an UnsupportedOperationException) or some
other appropriately named subclass of IllegalStateException. This
method throws an unspecified error instead, which is fundamentally wrong
because this is clearly not an every method, non-occurring, or recurring end-user
error. What if everyone did this?

If it makes sense for some classes to document that unspeci-


fied runtime exceptions are thrown, why not others?

The fallacy of this line of thinking is obvious. An ever increasing number of runt-
ime exceptions and errors would be coded as throw new Runtime-
Exception("detail message") and throw new Error("detail
message") . There must be a “line in the sand” drawn when it comes to docu-
menting that a method or constructor throws an unspecified runtime exception
or error.
It is easy to explain why checked exceptions must have names because cli-
ent programmers must be capable of explicitly catching them. Some runtime
exceptions and errors such as an OutOfMemoryError must also be explicitly
caught. They must be declared as RuntimeException or Error sub-
classes for the same reason as checked exceptions. Unspecified runtime excep-
tions and errors by definition cannot be explicitly caught. Furthermore, they are
rarely documented. Therefore the only rationale for declaring them as Run-
timeException or Error subclasses is that doing so is a programmer con-
vention. In the Java programming language exceptions have names. It is
that simple. This is an important programmer convention for the following rea-
sons.
• Names make it possible for programmers to communicate the details of a
runtime exception or error using only the exception name. This is particu-
larly important for runtime exceptions because they are routinely thrown
during development and unit testing. Take for example Arithmetic-
Exception . Except for BigDecimal and BigInteger in which
ArithmeticException is arguably misused as an Illegal-
ArgumentException, no class in the core API documents that it throws

750 JAVA RULES


ArithmeticException. Except for those two class, this exception is
always “tied to the current implementation”76 of a method or constructor.
Why not just throw new RuntimeException("/ by zero") instead
of declaring ArithmeticException? The exception name may only
ever be seen in a stack trace, but everyone immediately understands what
it means if you say that an ArithmeticException is thrown.
• The discipline of naming an exception and explaining why it is thrown is also
important in its own right. It forces a programmer to think more about the
cause of the exception. The next time you are tempted to use an unspeci-
fied runtime exception or error, make a point of using a Runtime-
Exception or Error subclass instead. My guess is that by the time
you are done declaring the new exception class (or deciding to use an
existing one) that you will have a better understanding of the circum-
stances under which it is thrown.
Because runtime exceptions are by definition recurring (at least during develop-
ment and unit testing), the argument against the use of unspecified runtime
exceptions is largely contained in the first bulleted item.
The argument against unspecified errors is much more difficult to make
because they are almost certain to be non-occurring errors. Why bother naming
an error if it never occurs? For example,

long l = unsafe.allocateMemory(8);
try {
unsafe.putLong(l, 0x0102030405060708L);
byte b = unsafe.getByte(l);
switch (b) {
case 0x01: byteOrder = ByteOrder.BIG_ENDIAN;
break;
case 0x08: byteOrder = ByteOrder.LITTLE_ENDIAN;
break;
default: throw new Error("Unknown byte order");
}
} finally {
unsafe.freeMemory(l);
}

Note that unsafe references an instance of the sun.misc.Unsafe class


which is a very special utility class for “low-level, unsafe operations” that can

ASSERTIONS, EXCEPTIONS, AND LOGGING 751


read from and write to arbitrary memory locations (hence “unsafe”) much like C
or C++ code. A try statement such as this is used in java.nio.Bits to
determine the byte order of the host operating system by writing an eight-byte
integer and reading back the first byte. The unspecified error is thrown in a logic
trap (the default label) that does not meet the “in complete control” muster of
assertions because programmers have no control over the byte order of the
host operating system. More so than any other I have seen this example makes
the case for the use of unspecified errors (a devil’s advocate example, if you
will):
• This is a one-of-a-kind error. None of the existing Error subclasses ade-
quately describe this error
• As would be expected, “unknown byte order” is a non-occurring error. Much
like AssertionError or SystemConfigurationError, the like-
lihood that such an error would be thrown is close to nil
• “Unknown byte order” is truly an unrecoverable error. Under no circum-
stances whatsoever should processing continue on a platform with an
unknown byte order. Had this method been fully instead of partially imple-
mented in the HotSpot VM, it most likely would have resulted in a VM crash
instead of an error being thrown
• This error is thrown during system initialization (at least in the J2SE) and
therefore cannot be caught by application programmers.
The last bulleted item more so than anything else makes “unknown byte order”
an exemplary unspecified error. The java.nio.Bits class is loaded before
any application code is executed, along with approximately 300 other classes
and interfaces in the J2SE. Application programmers do not have the opportu-
nity to catch errors thrown during system initialization.
If for no other reason than the java.nio programmers—who broke new
ground subclassing IllegalStateException and who from design to
documentation delivered the best software package in the core API to date—
chose to use an unspecified error in this case, I suspect that programmers will
continue to do so no matter what I say. Nevertheless, I would have coded

throw new UnknownByteOrderError("0x" + String.toHexString(b));

752 JAVA RULES


One should take pride in discovering a new non-occurring error such as this. My
whole argument against the use of unspecified errors is that the Error class
hierarchy as shown in Figure 6.4 is very finite. This of course is not the actual

Figure 6.4 Proper Error Subclasses

Error class hierarchy, but how I think it should look. The LinkageError
and VirtualMachineError subclasses have been omitted in order to sim-
plify the figure. The deprecated ThreadDeath has also been omitted. Both
the FactoryConfigurationError and TransformerFactoryCon-
figurationError classes have been removed and replaced by the more
general SystemConfigurationError class. That class has in turn been
extended by the only known recurring end-user errors. Finally both AWTError
and CoderMalfunctionError have been removed for reasons discussed
at the bottom of 6.5 The Throwable Class Hierarchy.
This may not be the actual Error class hierarchy, but we can nonetheless
think of it as being such. Additions of new non-occurring errors such as
UnknownByteOrderError are so rare that they should not be allowed to
stand in the way of a “no unspecified runtime exceptions or errors” programmer
convention. The good to be gained by such a programmer convention signifi-
cantly outweighs the inconvenience of having to name new non-occurring errors.

ASSERTIONS, EXCEPTIONS, AND LOGGING 753


6.5
NOTE The Chicken Little “the sky is falling” problem is largely historical. In pro-
cedural programming languages errors are “serious problems”77 that
result in abnormal program termination. This is not so in the Java
programming language. This “paradigm shift” is discussed at
length in the following section. It requires a much more precise
way of thinking about errors. They are no more “serious” than high-
level (checked) exceptions. Furthermore, the seriousness of an excep-
tion has no bearing on whether it should be checked or unchecked. The
API docs for the Error class are grossly misleading in this regard.

6.5.2.1 The Chicken Little “The Sky is Falling” Problem


Chicken Little, for those of you who do not know, was walking through the woods
one day when an acorn fell and hit her on the head. She concluded that the sky
was falling and then ran to tell the king (or end user) the bad news. The definition
of errors as “unrecoverable” suffers from what I like to describe as the Chicken
Little “the sky is falling” problem. This problem manifest itself in the use of

The Chicken Little “the sky is falling” problem is pervasive in the core
API. The only difference is that instead of saying “the sky is falling,”
the responsible programmer throws InternalError.

nondescript, —what I am calling unspecified—errors that make it anywhere


from “difficult to impossible” not to recover from the error, but merely to catch
it. More often than not the nondescript or unspecified error thrown in the core
API is InternalError.
I may be wrong about this, but whenever I see a core API programmer throw-
ing an unspecified error (InternalError in particular) I interpret it as an
attempt to more or less immediately shut down the application program. This
makes sense only if errors are defined as “unrecoverable.” Like much of what is

77. API docs for the java.lang.Error class.

754 JAVA RULES


wrong with exception handling on the Java platform, this problem has definite
roots in the JLS:
The class Error is a separate subclass of Throwable, distinct
from Exception in the class hierarchy, to allow programs to use the
idiom:

} catch (Exception e) {

to catch all exceptions from which recovery may be possible without


catching errors from which recovery is typically not possible.

The fundamental misconception of “unrecoverable errors versus recoverable


exceptions” can be traced directly to this specification for catchall exception
handlers in the JLS. This is not, however, the correct definition of errors. Here is
a synopsis of the definition of unchecked exceptions in 6.5 The Throwable
Class Hierarchy above:
Errors are narrowly defined categories of exceptions that must be
exempted from the compiler check for an exception handler either
because they potentially occur in almost every method or else because
they do not occur at all. Likewise, runtime exceptions are exempted
because they are not thrown in production. Exceptions that are not
thrown are not caught and therefore end up in every throws clause.
In the words of the JLS, declaring these exceptions would be “unimag-
inably inconvenient.”78 The design of both errors and runtime excep-
tions is to make checked exceptions a practical reality.

The word “recovery” is not mentioned once. There are three kinds of errors:
every method errors, non-occurring errors, and recurring end-user errors. Pro-
grammers cannot throw every method errors. Furthermore, new non-occurring
errors such as UnknownByteOrderError are about as rare as recurring
end-user errors. This line of reasoning is very significant for Java programmers
because the only errors that are left are AssertionError and System-
ConfigurationError (or the equivalent). Thus we are faced with two
radically different realities; one in which errors are vaguely defined as
“serious problems” that are “unrecoverable” versus (a much more mun-

78. Gosling et al., §8.4.4, “Method Throws.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 755


dane) one in which they are either assertions or system configuration
errors. If the reader finds this line of argument persuasive, the next step is to
answer the question of what to do with a “serious” exception that is neither an
assertion nor a system configuration error. In other words, if the sky is not fall-
ing, what just hit me on the head?
The answer to this question is to throw a high-level (checked) exception such
as PrintException. Think about this for a minute; in the context in which
they are thrown, high-level exceptions such as these are unrecoverable excep-
tions. These checked exceptions are every bit as “serious” as an error in that
they represent the failure of an entire package. (As defined in 6.5.1 General
Exception Classes, there is usually only one high-level exception per package
and it is always a checked exception.) It is notable that none of the classes in the
javax.print package throw an error (and there are a lot of them). This is as
it should be.79 Can you imagine crashing a critical application such as a flight avi-
onics system because a printer job fails? It simply does not matter how “serious”
the exception, there are innumerable applications that can continue to run even if
something as devastating as a paper jam occurs. (I am being facetious.)
The biggest problem with defining errors as “unrecoverable” exceptions is
that programmers will then throw an error whenever there is a “serious prob-
lem.” This knee-jerk response to serious problems (or “errors”) is an anachro-
nism in an object-oriented programming language such as Java, as outdated as
spaghetti code. It is Chicken Little crying that “the sky is falling.” Moreover, it is

After system initialization has completed, exception translation more


or less replaces the use of errors to shut down a program.

an improper use of errors (except during system initialization). In an object-ori-

79. Actually, I am contradicting myself. If for some reason the javax.print package used a
system property in which to store the name of a class that is a designated part of the J2SE or other
Java product, then it would be perfectly natural to throw a SystemConfigurationError if
for some reason that system property were null or the named class could not be found or instan-
tiated. The difference is that system initialization has not yet completed. Core API programmers rou-
tinely throw errors if there is a problem during initialization. See also the discussion of the “error
chute” in 6.8.1.1 Catchall Exception Handlers.

756 JAVA RULES


ented programming language, shutting down an application after system initial-
ization has completed is the prerogative of client programmers. Fortunately,
now that the overloaded stop methods in the Thread and ThreadGroup
classes have been deprecated, and ThreadDeath has, well, died, client pro-
grammers can use the catch(Throwable e) exception handler to regain
control from errant classes and subsystems that would use errors to shut down
an application. In other words, throwing an error is no longer tantamount to
invoking System.exit(1) . See 6.8.1.1 Catchall Exception Handlers below
for a complete discussion.
At this point, a practical example of the consequences of the Chicken Little
“the sky is falling problem” would be very useful. The ZipFile class (a super-
class of JarFile) in java.util is instantiated whenever a Zip or JAR file is
read. A number of bug reports have been filed against ZipFile for throwing
an InternalError in the nextElement() method while enumerating the
entries. The code originally looked like this:

public Object nextElement() throws NoSuchElementException {


if (i >= total) {
throw new NoSuchElementException();
}
long jzentry = getNextEntry(jzfile, i++);
if (jzentry == 0) {
throw new InternalError("jzentry == 0");
}

It helps to understand the “jz” notation. I believe this refers to “JAR or Zip file.”
The getNextEntry(long jzfile, int i) method is a native class
method that is passed an address for the cached JAR or Zip file information as
well as the entry number. It returns zero to indicate a failure. The total num-
ber of entries was returned by another native method that cached the JAR or
Zip file when it was initially read. The error is thrown because the getNext-
Entry method returns zero, but that does not explain what causes the error.
This example cannot be appreciated unless you accept the fact that the respon-
sible programmer had no idea why getNextEntry would return zero, but was
merely aware of the fact that it returned zero if something went wrong. I base

ASSERTIONS, EXCEPTIONS, AND LOGGING 757


this assumption on the detail message. Prior to the 1.3.1 release the top of the
stack trace would look like this:

java.lang.InternalError: jzentry == 0
at java.util.zip.ZipFile$2.nextElement(Unknown Source)

Beginning with the 1.4 release some additional diagnostic information has been
added to the detail message, which now looks like this:

java.lang.InternalError: jzentry == 0,
jzfile = 1190293256,
total = 38,
name = F:\Temp\db\inserted_2bytes.zip,
i = 1,
message = invalid LOC header (bad signature)
at java.util.zip.ZipFile$2.nextElement(ZipFile.java:303)

In either case, the detail message begins with the rather obtuse jzentry ==
0, which is the value of a local variable. This is the kind of diagnostic message
one would expect to see when using System.out.println to debug a pro-
gram. Because of this, I am utterly convinced that the responsible programmer
had no idea what might cause this exception to be thrown. Thus the only expla-
nation for throwing an error is that this was obviously a very “serious” exception.
Throwing InternalError because of the seriousness of an exception is very
different from any of the other uses of unspecified runtime exceptions and
errors discussed in the previous section.
As it turns out this exception is thrown for two reasons. The more important
of the two was a caching problem (there was no check to see if the JAR or Zip
file was modified after being cached) that has since been fixed (see Bug Id
4353705). The other reason is that the Zip or JAR file is corrupted. This example
is particularly interesting because ZipException has been part of the
java.util.zip package since the 1.0 release; yet if a Zip file is somehow
corrupted (an exceptional condition, but hardly something that “never occurs”),
an InternalError is thrown. Bug Id 4615343 is a request to throw
ZipException (a checked exception) instead. A better solution would be to
throw a CorruptedZipFileException subclass of ZipException
now that the cause of the exception is understood. The problem is that adding a

758 JAVA RULES


throws clause to the nextEntry() method breaks compatibility with exist-
ing binaries. A comparable change from ZipException to Currupted-
ZipFileException would have been a binary compatible change.

6.5.3 Asynchronous Exceptions


If asynchronous exceptions were “rare”83 when the JLS was originally written,
they are all but extinct today. This is therefore a very tenuous subject. The only
reason application programmers ever had for even being aware of the concept
of asynchronous exceptions is ThreadDeath. Now that the stop methods in
the Thread and ThreadGroup classes have been deprecated, however, the
concept of asynchronous exceptions involves JVM implementation
details that are of no practical significance to application programmers.
As stated in both the JLS and JVMS:
The Java platform permits a small but bounded amount of execution to
occur before an asynchronous exception is thrown. This delay is per-
mitted to allow optimized code to detect and throw these exceptions at
points where it is practical to handle them while obeying the semantics
of the Java programming language.

A simple implementation might poll for asynchronous exceptions at the


point of each control transfer instruction. Since a program has a finite
size, this provides a bound on the total delay in detecting an asynchro-
nous exception. Since no asynchronous exception will occur between
control transfers, the code generator has some flexibility to reorder
computation between control transfers for greater performance.80

In the C++ code that implements the HotSpot VM, these are referred to as safe-
points. This discussion of polling for asynchronous exceptions at safepoints is
very far from the practical reality of application programmers. Even 11.8.3 Asyn-
chronous Exceptions in the The Java Native Interface Programmer’s Guide
and Specification down plays their significance:

80. Gosling et al., §11.3.2, “Handling Asynchronous Exceptions” and Tim Lindholm and Frank Yellin,
§2.16.2, “Handling an Exception.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 759


The Java thread API that generates asynchronous exceptions,
Thread.stop, has been deprecated in Java 2 SDK release 1.2.
Programmers are strongly discouraged from using Thread.stop
because it generally leads to unreliable programs. This is particularly a
problem for JNI code. For example, many JNI libraries written today
do not carefully follow the rules of checking for asynchronous
exceptions described in this section.81 [emphasis added]

For JNI programmers to “carefully following the rules of checking for asynchro-
nous exceptions” roughly equates to the following statement in the API docs for
the ThreadDeath class:
An application should catch instances of this class only if it must clean
up after being terminated asynchronously. If ThreadDeath is caught
by a method, it is important that it be rethrown so that the thread actu-
ally dies.82

The catch(Throwable e) exception handler is now used in the core API


without checking for and rethrowing ThreadDeath. Likewise, JNI program-
mers “do not carefully follow the rules of checking for asynchronous excep-
tions.”
There is a dichotomy in Sun documentation in which asynchronous excep-
tions are sometimes only discussed in terms of the overloaded stop methods
in the Thread and ThreadGroup classes (as in the above quote from a
major specification) and the JLS and JVMS, both of which include Internal-
Error in the definition of asynchronous exceptions:
Asynchronous exceptions are rare. They occur only as a result of:

• An invocation of the stop method of class Thread or


ThreadGroup
• An internal error in the Java virtual machine [implementa-
tion] 83

81. Sheng Liang, The Java Native Interface Programmer’s Guide and Specification, (Boston,
Addison Wesley Professional, 1999), 11.8.3, “Asynchronous Exceptions.”
82. API docs for the java.lang.ThreadDeath class.
83. Gosling et al., §11.3.2, “Handling Asynchronous Exceptions” and Tim Lindholm and Frank Yellin,
§2.16.2, “Handling an Exception.” The bracketed “implementation” is only found in the JVMS.

760 JAVA RULES


Synchronicity is defined in terms of a single thread. To say that exceptions are
“synchronous” simply means that they occur within a given thread at a specific
point (the point-of-origin). The synchronicity of most exceptions is obvious
because you can see the throw statement. This includes exceptions thrown in
native methods. The stop methods are described as asynchronous because
one thread can stop another thread at any point.84 Everyone knows, however,
that the stop methods have been deprecated; the question is Internal-
Error. If a JVM throws InternalError is it an asynchronous exception?
The JLS says only that InternalError “is considered asynchronous”85 but
offers no explanation why. The Java Programming Language offers the fol-
lowing explanation:
…such exceptions are considered asynchronous because they are
caused by the execution of instructions in the virtual machine, not the
instructions in a program.86

With all due respect, this makes no sense. It implies that all of the exceptions
thrown by a JVM implementation are asynchronous. That clearly contradicts the
following statement about synchronous exceptions in the JLS.
These exceptions are not thrown at an arbitrary point in the program,
but rather at a point where they are specified as a possible result of an
expression evaluation or statement execution.87

Truth be told the definition of asynchronous exceptions is debated by the very


software engineers responsible for implementing them.
As implemented in the HotSpot VM, I would say the answer to the question of
“Is InternalError asynchronous?” is an unqualified No. Historically,
InternalError has been thrown using the exact same mechanism

84. Actually, “a small but bounded amount of execution [is permitted] to occur before an asynchro-
nous exception is thrown” (from both the JLS and JVMS). In the terminology of the HotSpot VM, this
is referred to as reaching a “safepoint.” I believe that it is because of this specification and the fact
that invoking a stop method does not immediately stop a thread that one HotSpot programmer
refers to invoking stop as a “quasi-asynchronous exception.” I have seen other comments made
by very knowledgeable programmers questioning if ThreadDeath is truly asynchronous.
85. Gosling et al., §11.3.2, “Handling Asynchronous Exceptions.”
86. Ken Arnold, James Gosling, and David Holmes, §8.2.2, “Asynchronous Exceptions.”
87. Gosling et al., §11.1, “The Causes of Exceptions.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 761


(which is decidedly not a C++ throw statement) as any other (synchro-
nous) exceptions.88 It therefore cannot be asynchronous. This may have
recently changed, however. The evaluation of Bug Id 4454115 (related to the
implementation of the java.nio package) includes a clear reference to an
“asynchronous InternalError”89 that is apparently thrown using an entirely
different mechanism—the same one that is used when throwing Thread-
Death . That mechanism was left in place because the stop methods are dep-
recated, not non-existent. I believe this was the first truly asynchronous
InternalError ever coded. The software engineer responsible for this
change in the HotSpot VM included the following comments in the evaluation of
Bug Id 4454115.
I have used InternalError rather than create a new subclass of Error.
I'm pretty sure it should be an Error rather than a RuntimeException.89

In light of the fact that both the JLS and JVMS describe Internal-
Exception as asynchronous and that this may in fact be the first truly asyn-
chronous InternalError ever coded (in a Sun implementation of the JVM),
these comments are almost comical.

6.6 Throwable Objects


Throwable objects serve two purposes. The first and most important purpose
is to find a matching catch clause using the class of the exception object. The
other is to capture failure information. As stated in the JLS:
Every exception is represented by an instance of the class Throw-
able or one of its subclasses; such an object can be used to carry
information from the point at which an exception occurs to the handler
that catches it.90

88. This applies to both the HotSpot VM and native methods using JNI (which are not part of the
JVM but are mentioned here for the sake of completeness). I do not have the source code for the
Classic VM but have reason to believe that it also threw InternalError using the same mecha-
nism as synchronous exceptions.
89. Evaluation of Bug Id 4454115.
90. Gosling et al., introduction to Chapter 11, “Exceptions.”

762 JAVA RULES


In this book (and others), that information is referred as failure information.
Reading the API docs for the Throwable class you might think that the stack
trace and detail message is the only failure information:
A throwable contains a snapshot of the execution stack of its thread at
the time it was created. It can also contain a message string that gives
more information about the error.91

This is clearly misleading, but a much more serious problem with the API docs
for the Throwable class is the matter of fact assumption that subclasses
should have a no argument constructor and one that takes a String argument
(for the detail message):
By convention, class Throwable and all its subclasses have two con-
structors, one that takes no arguments and one that takes a String
argument that can be used to produce an error message.91

If subclasses capture failure information, they should have neither. I will


now explain why.
Failure information should be stored in instance variables as well as being
included in detail messages. It is critically important to this discussion to under-
stand why. There are two contexts in which client programmers and field service
engineers process failure information: one is while reading a stack trace; the
other is programmatically in an exception handler. Formatted strings (or tex-
tual data) such as the stack trace and detail message are only useful in
one of those two contexts. Consequently programmers have been parsing
stack traces and detail messages for years. Doing so is very problematic, how-

Detail messages are completely useless in an exception han-


dler. They should therefore be regarded as merely a convenience for
client programmers and field service engineers reading a stack trace.

ever, because the interface contract for exception classes do not specify the
format of either of these strings. For example, the API docs for the

91. API docs for the java.lang.Throwable class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 763


FileNotFoundException class do not specify that the file name is
included in the detail message. The introduction of the getStackTrace()
method in the 1.4 release of the Throwable class remedied this problem for
stack traces. That method allows for “programmatic access the stack
trace…”92 The exact same thing needs to be done for the detail message.
The Throwable class, however, cannot possibly provide programmatic
access to the failure information in hundreds or thousands of different detail
messages. Doing so is the responsibility of the programmers who declare
Throwable subclasses.
The general rule for capturing failure information in a checked exception is
that at a minimum the information used to create the detail message
should be passed to the constructor and stored in instance variables. For
example,

public class FileNotFoundException extends java.io.IOException {


private String pathname;
public FileNotFoundException(String pathname, String reason){
super(pathname + (reason == null ? " " : " " + reason));
this.pathname = pathname;
}
public String getPathname() {
return pathname;
}
}

This fanciful version of FileNotFoundException is passed the pathname


instead of a detail message. That failure information is then stored in a private
instance variable for which there is a public accessor method. This is how to
provide “programmatic access” to the failure information in a detail message.

The minimum failure information for a checked exception is that


which is used to create the detail message.

Because there is only one constructor, the pathname is a required construc-

92. API docs for the getStackTrace() method in the java.lang.Throwable class.

764 JAVA RULES


tor parameter (as defined in 1.3.3 Constructors). This means that the detail
message must be formatted in the constructor. This is why the API docs for
the Throwable class are so misleading. For checked exceptions to have a
constructor that takes a String argument is arguably self-contradictory
because it almost always means that there is failure information that is not pro-
grammatically accessible. In some cases mutator methods are used to pass fail-
ure information, but client programmers are never required to do so in an
exception class that has either a no argument constructor or one that takes a
String argument.
Many exception classes should store references to the current object. One
of the FAQ in the “Programming With Assertions” document is interesting in this
regard:
Why doesn't an AssertionError allow access to the object that
generated it? Similarly, why not pass an arbitrary object from
the assertion to the AssertionError in place of a detail message?

Access to these objects would encourage programmers to attempt to


recover from assertion failures, which defeats the purpose of the facil-
ity.93

Indeed it would, and that is why you should always consider storing a reference
to the current object when declaring a checked exception. Doing so allows client
programmers access to the public interface of the object that caused the
exception. In fact I would go as far as to say that an Object current-
Object parameter should be as common as the String detailMessage
parameter in checked exceptions, if not more so.
What about runtime exceptions and errors? They are generally not caught
except in catchall exception handlers, so why bother storing failure information in
instance variables? This is a difficult question to answer because programmers will
always find a reason to catch an exception. Fortunately, the decision has already
been made because most runtime exceptions and errors are part of the core API.
Consistent with the API docs for the Throwable class, most of them (including

93. Unascribed, “Programming with Assertions” in the API docs for the 1.4 release, “Design FAQ -
The AssertionError Class.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 765


the checked exceptions) have a no argument constructor and one that takes a
String argument (which generally means that failure information is not store in
instance variables). In fact, none of the errors in the core API store failure informa-
tion in instance variables. Ignoring the org.omg packages,94 the only runtime
exceptions that have required constructor parameters were notably added in the
1.3 and 1.4 releases. In the 1.3 release, there is the following example from the
java.lang.reflect package.

public UndeclaredThrowableException(Throwable
undeclaredThrowable)
public UndeclaredThrowableException(Throwable
undeclaredThrowable, String s)

In the 1.4 release there are the following examples in the java.nio package:

public IllegalCharsetNameException(String charsetName)


public UnsupportedCharsetException(String charsetName)

And in the java.util.regex package:

public PatternSyntaxException(String desc, String regex, int


index)

In all three cases there are accessor methods that correspond to the required
constructor parameters. This shows clear evidence of a shift towards runtime
exceptions (and possibly errors) that store failure information. Missing-
ResourceException also follows this pattern, but it should have been
declared as a checked exception any way. The ArrayIndexOutOfBounds-
Exception and StringIndexOutOfBoundsException both have a
constructor that can be passed an index value (along with a no-arg constructor
and one that takes a String argument), but it is only used in formatting the
detail message.
You should not make too much out of this analysis of runtime exceptions and
errors in the core API. For many of them there simply is no failure information to
store. That is certainly the case with a NullPointerException, for exam-

94. I very often do ignore the org.omg package. The wholesale neglect of Java naming conven-
tions makes even reading their API docs a maddening experience.

766 JAVA RULES


ple. Capturing failure information in runtime exceptions and errors is something
that must be decided on a case by case basis. Some programmers have
lamented the fact that ClassCastException does not store failure informa-
tion. They would like to see both the type name in the cast operator and the
class of the object referenced at run time programmatically accessible. This is
an exception to the rule, however. There has not been very much complaining
about the lack of failure information in runtime exceptions and errors. Even if
there were, Sun does not seem at all inclined to fix any of the older exception
classes. It is noteworthy that FileNot-FoundException already has a
private constructor (invoked by native I/O methods) similar to the
public one in the example above. Yet the pathname is not saved to a read-
only instance variable.
What about the checked exceptions in the core API. Do they follow the rule
that at a minimum the failure information contained in a detail message should
be programmatically accessible? There are a significant number of checked
exceptions in the core API that store failure information, but only a handful of
them make the failure information required constructor parameters (by not sup-
porting a no-argument or String constructor). Here again, of those that do
make failure information required constructor parameters about half are recent
additions including the same java.util.regex and java.nio packages
cited above. I expect that this will become the norm for checked exceptions
starting in the 1.5 release, much like the typesafe enum pattern has replaced
the use of convenience constants in the core API.

6.6.1 The Loggable Interface


The Loggable interface is not part of the core API, but perhaps should be. It
consists of a single logException(PrintWriter log) method:

public interface Loggable {


public void logException(java.io.PrintWriter log);
}

The Loggable interface makes it possible for an exception class to write


directly to a log report. Doing so is merely an alternative to cramming everything

ASSERTIONS, EXCEPTIONS, AND LOGGING 767


into a detail message. Detail messages really should be regarded as overviews
(or one-sentence summaries) for someone reading a stack trace. See 6.10.1.2
The UncaughtException Class for an example of how to use the
Loggable interface in a top-level exception handler.

6.7 The throw Statement


The transfer of control that occurs as the result of throwing an exception may be
local (to a catch clause in the same method) or non-local. A non-local trans-
fer of control (a.k.a. a non-local jump) simply means that the catch clause
is in a different method. The following quote from Patrick Naughton’s book sug-
gests that the designers of the Java programming language were at least con-
cerned about the possibility that the exception mechanism would be misused as
if it were a goto:
It is important to think of try, throw, and catch as clean ways to handle
errors and unusual corner cases in your program logic. These primi-
tives should not be considered a general mechanism for non-local
branching as you might have used the goto keyword in some other lan-
guages.95

The throw statement is in fact a highly constrained goto. For example,

class Test {
public static void main(String[] args) {
try {
anotherMethod();
}
catch (goto e) { }
}
static void anotherMethod() {
throw new goto(); //non-local jump
}
}
class goto extends RuntimeException { }

If catch(goto e) is regarded as a label for an arbitrary block of code, this


construct is not unlike the use of a labeled break statement. In fact, there are

95. Patrick Naughton, The Java Handbook, 176.

768 JAVA RULES


only two differences. The first is that, to effect the transfer of control, you must
first create and then throw an exception rather than execute a break statement
with label. The second is that non-local jumps are possible using the exception
mechanism. Fortunately, such blatant abuses of the exception mechanism have
never been a problem on the Java platform.
A much more common abuse of the exception mechanism is a failure to
understand the basic definition of an “exception.” The API docs for the Excep-
tion class are worse than useless in arriving at a proper understanding of this
term. The following definition from the opening sentence of Chapter 11, “Excep-
tions” in the JLS is way too narrow.
When a program violates the semantic constraints of the Java program-
ming language, the Java virtual machine signals this error to the pro-
gram as an exception.

Bloch has an entire section on this subject entitled “Item 39: Use exceptions only
for exceptional conditions” in which he makes the following comments.
Exceptions are, as their name implies, to be used only for exceptional
conditions; they should never be used for ordinary control flow.96

This emphasis on “control flow” can also be found in the FOLDOC definition of
the term exception, which begins as follows.
An error condition that changes the normal flow of control in a pro-
gram…97

None of these definitions are adequate in my opinion. As simple as it may be, I


think the best definition of an exception is something you do not expect to hap-

96. Joshua Bloch, Effective Java Programming Language Guide, “Item 39: Use exceptions only
for exceptional conditions.”
97. Free Online Dictionary of Computing (FOLDOC), foldoc.doc.ic.ac.uk/foldoc/
foldoc.cgi?exception.

ASSERTIONS, EXCEPTIONS, AND LOGGING 769


pen. For example, Bloch uses an example of a checked exception in which “an

The best definition of an exception is SOMETHING YOU DO NOT


EXPECT TO HAPPEN (often described as an “abnormal” or “excep-
tional” condition). Furthermore, an exception by definition makes
it impossible to continue processing. This applies equally to runt-
ime exceptions, errors, and all other (checked) exceptions.

attempt to make a call on a pay phone fails because the caller has not deposited
a sufficient quantity of money.”98 His point is that “the exception should provide
an accessor method to query the amount of the shortfall so the amount can be
relayed to the user of the phone,”98 but there is nothing out of the “ordinary” in
inserting too few coins in a pay phone or vending machine. It happens all the
time. Thus the exception mechanism should not be used.
There is another example I am always reminded of in this context. It has
been in The Java Tutorial for many years now:

try {
while (true) {
price = in.readDouble();
in.readChar(); //throws out the tab
unit = in.readInt();
in.readChar(); //throws out the tab
char chr;
desc = new StringBuffer(20);
char lineSep =
System.getProperty("line.separator").charAt(0);
while ((chr = in.readChar() != lineSep) {
desc.append(chr);
}

System.out.println("You've ordered " + unit +


" units of " + desc + " at $" + price);
total = total + unit * price;
}

98. Bloch, Effective Java, “Item 40: Use checked exceptions for recoverable conditions and run-
time exceptions for programming errors.”

770 JAVA RULES


} catch (EOFException e) { }
System.out.println("For a TOTAL of: $" + total);
in.close();99

This example reads a binary file until EOFException is thrown. The problem
is that reaching EOF while reading an input file is something that is expected to
happen. The exception mechanism therefore should not be used. This is a com-
mon problem that stems in part from the fact that EOFException was mis-
named. The exception is intended to signify that there are not enough unsigned
bytes left in the input stream to covert to a particular primitive data type. This
would be indicative of a corrupted file, most likely one that has been inadvert-
ently truncated. The exception should have been named UnexpectedEOF-
Exception. As stated in the API docs:
Signals that an end of file or end of stream has been reached unexpect-
edly during input.100

The second paragraph of the API docs for this exception class originally read as
follows for many years starting with the 1.0 release all the way through the last
1.3 release:
This exception is mainly used by data input streams, which generally
expect a binary file in a specific format, and for which an end-of-stream
is an unusual condition. Most other input streams return a special value
on end of stream.101

To this was added the following innocuous paragraph in the 1.2 release:
Note that some input operations react to end-of-file by returning a dis-
tinguished value (such as -1) rather than by throwing an exception.102

As an example of what the second paragraph means by “a binary file in a spe-


cific format,” the DataIODemo example in The Java Tutorial expects a
sequence of records consisting of three fields: a double (the price), an int
(the number of units), and a String (the description) written out as a variable-

99. Mary Campione and Kathy Walrath, “How to Use DataInputStream and DataOutputStream.”
100. API docs for the java.io.EOFException class.
101. API docs for the java.io.EOFException class prior to the 1.4 release.
102. API docs for the java.io.EOFException class in the 1.2 and 1.3 releases only.

ASSERTIONS, EXCEPTIONS, AND LOGGING 771


length sequence of Unicode characters. What would happen if the file had been
inadvertently truncated and an EOFException were thrown while reading a
double ? The answer is that this failure is effectively not caught. The output
would be incorrect but the program would continue executing as if nothing had
happened.
In response to Bug Id 4269112, the interface contract for EOFException
was substantially changed in the 1.4 release to read as follows.
Signals that an end of file or end of stream has been reached unexpect-
edly during input.

This exception is mainly used by data input streams to signal


end of stream. Note that many other input operations return a special
value on end of stream rather than throwing an exception.103 [empha-
sis added]

Inasmuch as I believe this is a fundamental error that will eventually have to be


corrected, it is worth taking a look at the description of this bug report:
Description: The javadoc for java.io.EOFException states:

“Signals that an end of file or end of stream has been reached


unexpectedly during input.

This exception is mainly used by data input streams, which


generally expect a binary file in a specific format, and for
which an end of stream is an unusual condition. Most other
input streams return a special value on end of stream.”

However, DataInputStream.readInt, DataInputStream.readBoolean, etc.


have no way of signalling an end-of-file condition except by throwing
this exception.

A typical reading loop from a DataInput stream is:

try {
for (;;) {
int i = in.readInt();
// …
}

103. API docs for the java.io.EOFException class starting in the 1.4 release.

772 JAVA RULES


} catch (EOFException e) {
in.close();
} catch (IOException e) {
// … handle an error
}

This is in contrast to the typical InputStream or Reader loop:

try {
while ((ch = in.read()) != -1)
// …
in.close();
} catch (IOException e) {
// … handle an error
}

This behavior is noted in the Java Programming Language, 2nd Edition,


Gosling/Arnold, Sec. 12.21, p. 256.

Since an EOFException can be thrown in this expected situation, the


javadoc should be updated to reflect this.104

To allow this change from an “unexpected” EOFException to an “expected”


EOFException to succeed sanctions the kind of latent bugs in code such as
the example from The Java Tutorial above. It effectively negates the original
intent of the exception. Consider for example the source code for the read-
Int() and readBoolean() methods:

public final int readInt() throws IOException {


InputStream in = this.in;
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}

public final boolean readBoolean() throws IOException {


int ch = in.read();

104. Description of Bug Id 4269112.

ASSERTIONS, EXCEPTIONS, AND LOGGING 773


if (ch < 0)
throw new EOFException();
return (ch != 0);
}

The readInt() method throws EOFException “if this input stream


reaches the end before reading four bytes.”105 There is no way of determining if
the EOF marker was read in the first, second, third, or fourth unsigned byte. If
EOFException is being used as an EOF marker and the file consists only of
the int data type (as in the example from this bug report), one would expect
the EOF marker to appear in ch1. If it appears in ch2, ch3, or ch4, however,
the file is definitely corrupted. The problem again is that DataInputStream
throws the same exception in both cases because it was asked to read an int
and could not. Interpreting EOFException as an EOF marker is therefore like
someone telling you the file you are reading is corrupted and then you saying,
“Okay, I’m done reading this file.”
The readBoolean() method throws EOFException “if this input
stream has reached the end,”106 as does the readByte() and readUn-
signedByte() methods. This is because these methods read only one
unsigned byte at a time from the input steam. If, and only if, you are reading a
file that consists of no other primitive data types than boolean values
and signed or unsigned bytes, (an “unexpected”) EOFException works
the same as an EOF marker. You can see this much in the source code for the
readBoolean() method above. However, binary files almost always include

Using EOFException as an EOF marker is slumming. There is no


such thing as an EOF marker when reading binary files that include
multi-byte data types (otherwise known as records).

multi-byte data types such as int, double, and char. In that case, the
DataInputStream class does not in fact include a mechanism for signaling
that EOF was reached. Perhaps it should. When reading the first unsigned byte of

105. API docs for the readInt() method in the java.io.DataInputStream class.
106. API docs for the readBoolean() method in the java.io.DataInputStream class.

774 JAVA RULES


a multi-byte data type, perhaps the DataInputStream should throw an EOF-
Exception versus an UnexpectedEOFException, but the java.io
package is not designed that way.
How then do you determine when a DataInputStream has reached end
of file? This is the real problem; it takes more code to do this correctly, espe-
cially when there are variable-length fields such as desc (the description) in the
example from The Java Tutorial. I will now use the examples in The Java
Tutorial and Bug Id 4269112 to show how to detect EOF in binary files that
include multi-byte data types (in other words, how to read records in the Java
programming language).
Most of the problems with the example in The Java Tutorial begin on the
output side. For one, tab-delimiters are normally only used in text files. I espe-
cially do not think platform-specific line separator characters should be used as
record separators in the Java programming language. Doing so is very problem-
atic. Neither delimiters nor record separators should be used when writing binary
data. They simply are not needed. If the writeChars(String s) method is
to be used to write strings to binary files, then a length field should be written
immediately before it. For example,

import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
File file = new File("invoice1.txt");
DataOutputStream out = new DataOutputStream(
new FileOutputStream(file));
double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
int[] units = { 12, 8, 13, 29, 50 };
String[] descs = { "Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain" };
for (int i = 0; i < prices.length; i ++) {
out.writeDouble(prices[i]);
out.writeInt(units[i]);
out.writeInt(descs[i].length());
out.writeChars(descs[i]);
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 775


out.close();

DataInputStream in = new DataInputStream(


new FileInputStream(file));
long fileLength = file.length();
long bytesRead = 0;
double total = 0.0;
try {
while (bytesRead < fileLength) {
double price = in.readDouble();
int unit = in.readInt();
int numChars = in.readInt();
StringBuffer desc = new StringBuffer(numChars);
for (int i=0; i < numChars; i++)
desc.append(in.readChar());
System.out.println("You've ordered " + unit +
" units of " + desc + " at $" + price);
total = total + unit * price;
bytesRead += 16 + (numChars*2);
assert bytesRead <= fileLength;
}
} catch (IOException e) {
System.err.println(e);
System.exit(1);
} finally {
in.close();
}
System.out.println("For a TOTAL of: $" + total);
}
}

This is the exact same program as DataIODemo.java in The Java Tutorial


(which explains why the output stream is not buffered), only EOFException is
not used as if it were an EOF marker.
The example in Bug Id 4269112 has an even more straightforward solution
because there are no variable length fields. For example,

long fileLength = file.length();


try {
/*
* Files in which records are stored should be
* multiples of the record size
*/

776 JAVA RULES


if (fileLength % 4 != 0)
throw new IOException("file length " +
fileLength + " not a multiple of 4");
long numRecs = fileLength / 4;
for (int i=0; i < numRecs; i++) {
System.out.println(in.readInt());
}
} catch (IOException e) {
System.err.println(e);
System.exit(1);
} finally {
in.close();
}

The first thing that must be done when reading binary data is to invoke the
length() method in the File class. One way or another, the file length is
used to determine when the end of a binary file is reached, not EOF-
Exception.
Note that it is even possible to subclass DataInputStream and add an
iterator() method that returns an Iterator for which hasNext() has
the meaning of “has another record.” The core API could have generalized this
behavior by creating a RecordInputStream class. The constructors for
such a class would throw a InvalidFileLengthException (which is an
IOException subclass) if the file length was not evenly divisible by the record
length passed. The next() method would throw EOFException if for some
reason the total number of records computed in the constructor could not be
read. RecordInputStream subclasses could further simplify record pro-
cessing by overriding the next() method and reading each of the fields in a
given binary data file format into appropriately named instance variables.
The more important point in terms of this section is that reaching the end of
a binary file (or any other file for that matter) is something that is expected to
happen. Therefore the exception mechanism should not be used. Besides the
fact that the examples in The Java Tutorial and Bug Id 4269112 include latent
bugs, the reason for not using the exception mechanism for “ordinary” or “nor-
mal” control flow is that doing so is very inefficient. I was first made aware of this

ASSERTIONS, EXCEPTIONS, AND LOGGING 777


while reading Bloch’s book. In “Item 39: Use exceptions only for exceptional con-
ditions” he makes the following two points.
• Because exceptions are designed for use under exceptional
circumstances, few, if any, JVM implementation attempt to
optimize their performance. It is generally expensive to cre-
ate, throw, and catch an exception.
• Placing code inside a try-catch block precludes certain
optimizations that modern JVM implementation might other-
wise perform.107
Anyone who has read 6.4 The Exception Mechanism should understand why the
exception mechanism is slow. Finding a matching catch clause requires
one or more table lookups. For example, here is the exception table for one
of the microbenchmark tests below:

Exception table:
from to target type
41 44 44 <Class Zero>
41 44 57 <Class One>
41 44 70 <Class Two>
41 44 83 <Class Three>
41 44 96 <Class Four>
41 44 109 <Class java.lang.Throwable>

The assembler code that implements the table lookup is part of the athrow
instruction. It cannot be seen in decompiled code.
Microbenchmark tests show that these table lookups are much slower than
comparable control-flow statements. Bloch claims that the comparable control-
flow statements are seventy times faster. I was a little sceptical of that number
and ran the test myself. Here is the source inspired by his example in Effective
Java:
import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
test1(1000, true);
test2(1000, true);
System.out.println();

107. Bloch, Effective Java, “Item 39: Use exceptions only for exceptional conditions.”

778 JAVA RULES


test1(10000, false);
test2(10000, false);
System.out.println();
test1(100000, false);
test2(100000, false);
System.out.println();
test1(1000000, false);
test2(1000000, false);
}
static void test1(int count, boolean JVMwarmup)
throws IOException {
byte[] a = new byte[100];
Microbenchmark timer = new Microbenchmark().start();
for (int j=0; j<count; j++) {
try {
int i = 0;
while (true)
a[i++]++;
} catch (ArrayIndexOutOfBoundsException e) { }
}
timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using the exception mechanism " +
timer.getElapsedTime());
}
}
static void test2(int count, boolean JVMwarmup)
throws IOException {
byte[] a = new byte[100];
Microbenchmark timer = new Microbenchmark().start();
for (int j=0; j<count; j++) {
for (int i=0; i < a.length; i++)
a[i]++;
}
timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using a standard for loop " +
timer.getElapsedTime());
}
}
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 779


Here is some typical output on my machine:

10000 using the exception mechanism 141


10000 using a standard for loop 0

100000 using the exception mechanism 1343


100000 using a standard for loop 47

1000000 using the exception mechanism 13438


1000000 using a standard for loop 515

Ignoring the first test, these results suggest that the exception mechanism is
almost 30 times slower than comparable control-flow statements. As stated
above, Bloch reported 70 times slower. In either case, the difference is signifi-
cant. What about control-flow statements other than loops? The following
microbenchmark test suggests that the exception mechanism is about 18 times
slower than a comparable if-then-else or switch statement.

import java.util.Random;
class Test {
static Random random = new Random();
static int zero, one, two, three, four;
static Number[] exceptions = {new Zero(), new One(), new Two(),
new Three(), new Four()};
static Throwable t;
public static void main(String[] args) {
test1(100000, true);
test2(100000, true);
test3(100000, true);
System.out.println();
test1(1000000, false);
test2(1000000, false);
test3(1000000, false);
System.out.println();
test1(10000000, false);
test2(10000000, false);
test3(10000000, false);
System.out.println();
test1(100000000, false);
test2(100000000, false);
test3(100000000, false);
}

780 JAVA RULES


static void test1(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
int n = random.nextInt(5);
t = exceptions[n]; //algebra
if (n == 0)
zero++;
else if (n == 1)
one++;
else if (n == 2)
two++;
else if (n == 3)
three++;
else if (n == 4)
four++;
else
assert false;
}
timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using if-then-else statements " +
timer.getElapsedTime());
}
}
static void test2(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
int n = random.nextInt(5);
t = exceptions[n]; //algebra
switch(n) {
case 0: zero++;
break;
case 1: one++;
break;
case 2: two++;
break;
case 3: three++;
break;
case 4: four++;
break;
default: assert false;
}
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 781


timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using switch statement " +
timer.getElapsedTime());
}
}
static void test3(int count, boolean JVMwarmup) {
Microbenchmark timer = new Microbenchmark().start();
for (int i=0; i<=count; i++) {
/*
* As sort of an algebraic way of balancing these
* microbenchmark tests an array access expression
* has been added to the other tests and a class
* instance creation expression has been added to
* this one.
*/
t = new Throwable(); //this cost must be incurred
int n = random.nextInt(5);
t = exceptions[n];
try {
throw t;
} catch(Zero e) {
zero++;
} catch(One e) {
one++;
} catch(Two e) {
two++;
} catch(Three e) {
three++;
} catch(Four e) {
four++;
} catch(Throwable e) {
assert false;
}
}
timer.stop();
if (!JVMwarmup) {
System.out.println(count +
" using throw statement " +
timer.getElapsedTime());
}
}
}

782 JAVA RULES


class Number extends Throwable {}
class Zero extends Number {}
class One extends Number {}
class Two extends Number {}
class Three extends Number {}
class Four extends Number {}

Here is some typical output on my computer:

1000000 using if-then-else statements 219


1000000 using switch statement 203
1000000 using throw statement 3531

10000000 using if-then-else statements 2250


10000000 using switch statement 2063
10000000 using throw statement 35375

100000000 using if-then-else statements 22500


100000000 using switch statement 20671
100000000 using throw statement 353469

I regard this as a somewhat flawed microbenchmark test because of what is


described as “algebraic” additions in the source code, but it is reasonably close.
Okay so the exception mechanism is slow because of table lookups. That
much I can understand, but what about Bloch’s claim that “placing code inside a
try-catch block precludes certain optimizations that modern JVM implemen-
tation might otherwise perform.”107 This suggests something entirely different
than only using the exception mechanism for the unexpected. To say that code
inside a try block cannot be optimized (or fully optimized) suggests that pro-
grammers should strive to minimize the amount of code in a try block.
This is not universally understood. For example, I found the following comment
and try block in the core API.

/* Since try/catch is free, except when the exception is thrown,


put in this extra try/catch to catch negative indexes and
display a more informative error message…

try {
return elementData[index];
} catch (ArrayIndexOutOfBoundsException e) {

ASSERTIONS, EXCEPTIONS, AND LOGGING 783


throw new ArrayIndexOutOfBoundsException(index + " < 0");
}

If putting code inside of a try block precludes certain JVM optimizations, then it
is not free. It bothers me that Bloch offers no supporting documentation for this
claim. On the other hand, I have no reason to doubt what he is saying, especially
when you consider that Effective Java was technically edited by “the best team
of reviewers imaginable”108 including no less than Dr. Gosling, Gilad Bracha,
Peter Haggar, Doug Lea, Tim Lindholm, and others. The only supporting docu-
mentation that I can find, however, is related to asynchronous exceptions (which
even if they no longer exist continue to influence the design of modern JVM such
as HotSpot). The throw statement is a “control transfer” as referred to in the
following specification that can be found in both the JLS and JVMS.
A simple implementation might poll for asynchronous exceptions at the
point of each control transfer instruction. Since a program has a finite
size, this provides a bound on the total delay in detecting an asynchro-
nous exception. Since no asynchronous exception will occur between
control transfers, the code generator has some flexibility to reorder
computation between control transfers for greater performance.109

Comparable control-flow statements such as are used in the microbenchmark


tests above are not control transfers .
The remainder of this section discusses the fact that the stack trace is
“filled” when a Throwable object is created, not when it is thrown. That this is
not entirely intuitive can be seen in Bug Id 4015943 (a very old bug report dated
November 21, 1996), which complained matter of factly that the stack trace
should reflect the point at which an exception is thrown. Of course it was marked
“Closed, not a bug,” but the evaluation is interesting:
You can call throwable.fillInStackTrace() if you want to
refill the backtrace.

108. Bloch, Effective Java, “Acknowledgments.”


109. Gosling et al., §11.3.2, “Handling Asynchronous Exceptions” and Tim Lindholm and Frank
Yellin, §2.16.2, “Handling an Exception.”

784 JAVA RULES


This behavior was created to allow an exception handler to look at an
exception and rethrow it if it didn't want to handle it…110

All Throwable constructors invoke the native fillInStackTrace()


method that takes a “snapshot” of the execution stack, otherwise known as a
stack trace (or backtrace). This does not necessarily reflect when the exception
is thrown. For example,

class Test {
public static void main(String[] args) throws Exception {
anotherMethod(new Exception());
}
static void anotherMethod(Exception e) throws Exception {
throw e;
}
}

Executing this program prints the following stack trace.

Exception in thread "main" java.lang.Exception


at Test.main(Test.java:3)

This is admittedly a contrived example, but it shows the potential consequences


of creating Throwable objects far from where they are thrown. The result is a
misleading stack trace. In this case, the stack trace does not even include the
name of the method in which the exception was thrown.

6.8 The try Statement


There are three blocks in a try statement: the try block, the catch block,
and the finally block. As if -then and if-then-else are considered to be
different kinds of statements, so are try-catch, try-catch-finally,
and try-finally statements. The general form of a try statement is as fol-
lows.

try { }
catch (Throwable e) { }

finally { }

110. Evaluation of Bug Id 4015943.

ASSERTIONS, EXCEPTIONS, AND LOGGING 785


The ellipsis indicates that there can be any number of catch clauses in a try
statement. Assuming that there is at least one catch clause, the finally
clause is optional. There must be at least one catch or finally clause in
every try statement. Otherwise, a compiler error is generated because a try
statement without a catch or finally clause makes no sense.
The order of execution for try statements is as follows.
1. If the try block completes normally, execution continues at the first state-
ment after a try-catch. In the case of a try-finally or try-
catch-finally statement, the finally block is executed first. If
execution of the try block completes abruptly because an exception is
thrown, execution stops at the precise point (the point-of-origin) at which an
exception is thrown.
2. If the expression in the throw statement is a class instance creation
expression, a Throwable object is created. This causes a native
method in the Throwable class to be invoked. The native method
stores information about methods on the stack at the time the Throwable
object is created. This information is later used to print a stack trace.
3. The execution stack is traversed (starting with the current method at the top
of the stack) searching for the first dynamically enclosing catch clause
with a parameter type to which the class type of the exception object is
assignable. That is to say, the search for a matching catch clause begins.
As explained in 6.4 The Exception Mechanism, this is a simple table lookup
involving the per-method exception table that is a part of every frame on the
stack.
4. As the stack is traversed, if there is no matching catch clause in the
exception table for a method, the frame for that method is discarded.
Before doing so, however, if a try-finally or try-catch-
finally statement was executing at the time the exception was thrown,
the finally block is executed. In addition, any locks held by the method
are also released.
5. If a matching catch clause is found, a reference to the Throwable
object is assigned to the exception-handler parameter and execution contin-
ues in the catch block. After the catch block completes either normally
or abruptly and before control is transferred out of the try statement, the
finally block of a try-finally or try-catch-finally state-

786 JAVA RULES


ment must be executed. If no matching catch clause is found, the
uncaughtException(Thread t, Throwable e) method in the par-
ent ThreadGroup is invoked. This method is referred to as the default
exception handler.
Uncaught exceptions and the default exception handler are discussed at length
in 6.10 Uncaught Exceptions.
There are a couple of very important rules to consider when coding a try-
finally or try-catch-finally statement. The first is that the value of
an expression returned in either a try or catch block cannot be changed as
the result of executing a finally block. For example,

import java.io.*;
public class Test {
String s = "field";
public static void main(String args[]) throws IOException {
System.out.println(new Test().testLocalVariable());
System.out.println(new Test().testField());
}
String testLocalVariable() {
String s = "local variable";
try {
return s;
} finally {
s = "finally";
}
}
String testField() {
try {
return s;
} finally {
s = "finally";
}
}
}

Executing this program prints:

local variable
field

ASSERTIONS, EXCEPTIONS, AND LOGGING 787


This behavior is not, nor has it ever been, documented in the JLS. The specifica-
tion says only:
If execution of the try block completes abruptly for any other reason
R, then the finally block is executed. Then there is a choice:

° If the finally block completes normally, then the try


statement completes abruptly for reason R …111
It is not enough to say that “the try statement completes abruptly for reason
R.” In fact, it is not even enough to say that the expression in the return state-
ment is fully evaluated (which the JLS does not say). In this case, the JLS should
explicitly state that the value of the fully evaluated expression is saved in
the local variable array while the finally block is executed. Thus it is
not possible to change the value, not even if it is an expression that denotes a
variable to which a different value is assigned in the finally block.
This behavior is interesting in that it is often said that return statements
should never be coded in a finally block, but that is the only way to change
the value returned either in the try block or in a catch block. Bug Id
4088715, “Warning for return expression lost due to abrupt transfer of finally
clause” is interesting in this regard. The evaluation of this bug includes the fol-
lowing comments.
…I can imagine a legitimate use for code which overrides a return
value in a finally: a transaction system which aborts and returns an
error while trying to commit in the finally…112

You can avoid coding control-transfer statements in a finally block as a


matter of style, but there is no way that such a construct should be considered
illegal. At most, a future version of javac may issue a warning in case this is
being done inadvertently.
The problem with coding a control-transfer statement such as return in a
finally block is that it masks any exception thrown either in the try
block or in a catch block. For example,

111. Gosling et al., The Java Language Specification, §14.19.2, “Execution of try-catch-finally.”
112. Evaluation of Bug Id 4088715.

788 JAVA RULES


class Test {
public static void main(String[] args) {
try {
for (int i=0; i < 4; i++)
System.out.print(amnesia(i));
} catch (Throwable e) {
System.out.println(e.getMessage());
}
}
static String amnesia(int i) {
String s = null;
boolean looping = true;
loop: while (looping ) {
try {
throw new Error();
}
finally {
looping = false;
switch (i) {
case 0: s = "A finally block with a control transfer ";
break loop;
case 1: s = "statement is like a catch(Throwable e) ";
continue loop;
case 2: return "exception handler that ignores ";
case 3: throw new Error("the exception thrown");
}
}
}
return s;
}
}

This program executes all four control-transfer statements in a finally block


instead of in the try block. As a consequence, the try block repeatedly throws

Do not code control-transfer statements in a finally block. That


includes explicitly throwing an exception.

an error that is never propagated. Bruce Eckel refers to this as the lost excep-
tion in his book Thinking in Java.113 The JLS addresses this issue as follows.

ASSERTIONS, EXCEPTIONS, AND LOGGING 789


If a finally clause is executed because of abrupt completion of a
try block and the finally clause itself completes abruptly, then
the reason for the abrupt completion of the try block is discarded and
the new reason for abrupt completion is propagated from there.114

It can be avoided only to the extent that exceptions are not explicitly thrown in
a finally block. Executing this program prints

A finally block with a control-transfer statement is like a


catch(Throwable e) exception handler that ignores the exception
thrown

In all four cases, the try statement forgets about the Error thrown in the try
block (hence the method name amnesia), instead executing the control-trans-
fer statement in the finally block. Note that exception chaining is not an
option, so the checked exception thrown in the try block is truly lost. Thus the
general rule is not to code control-transfer statements in a finally
block.
Application programmers have control over the decision to code a break,
continue, or return statement in a finally block. What they cannot nec-
essarily control are methods and constructors invoked in a finally block that
propagate exceptions. Thus the single most important point to understand about
the order of execution in a try statement is that if an exception is thrown in the
finally block, the execution of any control-transfer statement either in
the try block or in a catch block completes abruptly. The following
inverse example should make this abundantly clear.

class Test {
public static void main(String[] args) {
for (int i=0; i<4; i++) {
try {
test(i);
System.out.println("never returns");
} catch(Throwable e) {
System.err.print(e.getMessage());

113. Bruce Eckel, Thinking in Java, 3rd Edition, Beta (Upper Saddle River: Prentice Hall, 2002),
455.
114. Gosling et al., §11.3, “Handling of an Exception.”

790 JAVA RULES


}
}
}
static void test(int i) {
loop: while (true) {
try {
switch (i) {
case 0: break loop;
case 1: continue loop;
case 2: return;
case 3: throw new Error("is never thrown");
}
} finally {
//the propagate() method may not be documented
//to throw an Error
propagate(i);
}
}
System.out.println("never breaks or continues");
}
static void propagate(int i) {
throw new Error(Integer.toString(i));
}
}

Executing this program prints 0123. All four control-transfer statements in the
try block completed abruptly because of the error propagated in the finally
block.

6.8.1 The catch Clause


Another name for the catch clause in a try statement is an exception han-
dler. Anywhere the term exception handler is used, catch clause can be
substituted, and vice versa. The general form of a catch clause is as follows.

catch (Throwable e) {

}

The catch keyword is followed by the exception-handler parameter type


(which is always an instance of the Throwable class) and name enclosed in
parentheses and then the catch block. An exception-handler parameter is ini-

ASSERTIONS, EXCEPTIONS, AND LOGGING 791


tialized with a reference to the Throwable object before the catch block is
executed, not unlike the initialization of method and constructor parameters. It is
effectively destroyed when the catch block is exited.
If the type of the expression in a throw statement is not the same as the
type of the exception-handler parameter in the matching catch clause, there is
an implicit type conversion. For example,

import java.io.*;
class Test {
public static void main(String[] args) {
try { throw new EOFException(); }
catch (IOException ioe) {
System.out.println("this message prints");
}
}
}

I refer to this as the exception-handling conversion context. It is one of the


six implicit conversion contexts discussed in 5.7 Conversion Contexts.
If the type of the exception-handler parameter is a superclass somewhere in
the Throwable class hierarchy, the exception handler is sometimes referred
to as a general exception handler, which simply means that more than one
class of exceptions is handled. Most general exception handlers catch high-level
exceptions as defined in 6.5.1 General Exception Classes. If the exception-han-
dler parameter type is Exception or Throwable, I refer to the exception
handler as a catchall exception handler, which is the subject of the following
subsection. A catchall exception handler is a very special kind of general excep-
tion handler.
The order of catch clauses is significant. Here is a very common example
from the core API:

public void write(String s, int off, int len) {


try {
…code to write the string to an output stream…
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}

792 JAVA RULES


catch (IOException x) {
trouble = true;
}
}

This particular example is a simplified implementation of the write(String


s, int off, int len) method indirectly invoked by the overloaded
System.out.print and System.out.println methods. All of the
checked exceptions in the java.io package are IOException subclasses,
which makes catch(IOException e) a very common general exception
handler. Why must InterruptedIOException be caught separately?
According to Doug Lea, InterruptedIOException “was only partially
implemented, and only on some platforms.”115 What you are seeing here is a
standard idiom used in the java.io package to translate InterriptedIO-
Exception into an InterruptedException. The PrintStream class
(from which this example is taken) is designed not to throw exceptions. Nor-
mally, when an exception is thrown, a boolean flag named trouble is set to
true (as shown in the example). InterruptedIOException must be
handled separately, however, in order to preserve the interrupt status because
InterruptedIOException is a subclass of IOException.
If the order of the catch clauses in the previous example are reversed, the
following compiler error is generated (ignore the line number):

Test.java:19: exception java.io.InterruptedIOException has


already been caught
catch (InterruptedIOException x) {
^
1 error

This compiler error is roughly equivalent to an unreachable statement. In fact,


the error message in older versions of the javac compiler was catch not
reached.

115. Doug Lea, Concurrent Programming in Java, Second Edition, (Boston, Addison-Wesley,
2002), §3.1.2.2, “IO and resource revocation.” (See footnote at the bottom of page 173.)

ASSERTIONS, EXCEPTIONS, AND LOGGING 793


As of this writing, there are some relatively minor problems with both the
specification for unreachable catch clauses and how it is implemented in the
javac compiler. The specification reads as follows.
A catch block C is reachable [if, and only if,] both of the following are
true:

° Some expression or throw statement in the try block is


reachable and can throw an exception whose type is assign-
able to the parameter of the catch clause C. (An expres-
sion is considered reachable [if, and only if,] the innermost
statement containing it is reachable.)
° There is no earlier catch block A in the try statement
such that the type of C's parameter is the same as or a sub-
class of the type of A 's parameter.116
This specification explains why a checked exception cannot be caught unless it
is actually thrown in the try block (emphasis on checked). For example,

import java.io.IOException;
class Test {
public static void main(String[] args) {
try { }
catch (IOException ioe) { }
}
}

Attempting to compile this program generates the following compiler error:

Test.java:5: exception java.io.IOException is never thrown in


body of corresponding try statement
catch (IOException ioe) { }
^
1 error

The problem with the specification is that it cannot be applied to runtime excep-
tions and errors because “it is beyond the scope of the Java programming lan-
guage, and perhaps beyond the state of the art, to include sufficient information
in the program to reduce to a manageable number the places where these can
be proven not to occur.”117 Although the specification as written fails to exempt

116. Gosling et al., The Java Language Specification, §14.20, “Unreachable Statements.”

794 JAVA RULES


runtime exceptions and errors, the evaluation of Bug Id 4046575, “spec for
reachability doesn't consider unchecked exceptions” makes it clear that the
compiler is not likely to change:
This problem still exists in 1.3 and Merlin. However, I believe this is a
specification issue rather than a compiler issue. As exceptions Run-
timException [sic] and Error are unchecked (JLS 11.2), they can possi-
bly be thrown asynchronously. So the compiler treats all catch blocks
that accept super or subclasses of RuntimeException or Error as
reachable. But unchecked exceptions are not (and should be) dis-
cussed in the specification of statement reachability in JLS section
14.21.118

That this is being regarded as a specification problem is somewhat obvious


when you consider that this bug was submitted on April 22, 1997.
Only the Exception class per se, runtime exceptions, and errors can be
caught regardless of the code in the corresponding try block. The special case
for the Exception class is explained in the following quote from the JLS:
The class Error is a separate subclass of Throwable, distinct
from Exception in the class hierarchy, to allow programs to use the
idiom:

} catch (Exception e) {

to catch all exceptions from which recovery may be possible without


catching errors from which recovery is typically not possible.119

For the exact same reason, catch(Throwable e) must always be consid-


ered reachable. The specification for unreachable catch clauses would be a
serious problem if catchall exception handlers could not be coded.
Despite the powerful arguments made in the comments section of Bug Id
4046575 (by a JDC member who I particularly admire), the only compiler
changes I see (possibly) being made are the following.

117. Gosling et al., The Java Language Specification, §8.4.4, “Method Throws.”
118. Evaluation of Bug Id 4046575.
119. Gosling et al., §11.5, “The Exception Hierarchy.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 795


• An empty try block should not be allowed in a try-catch or try-
catch-finally statement. For example,
class Test {
public static void main(String[] args) {
try { }
catch (Exception e) { }
}
}

This catch clause is clearly not reachable. The jikes compiler


team has always issued a warning such as the following when the
try block of a try-catch or try-catch-finally state-
ment is empty.
catch (Exception e) { <---------> *** Warning: This catch
block is unreachable: there is no exception whose type is
assignable to "java/lang/Exception" that can be thrown during
execution of the body of the try block.

On the other hand, allowing empty catch blocks to compile could be


regarded as a convenience for programmers. Furthermore, in cases such
as this when the type of the exception-handler parameter is Exception or
Throwable , to issue a warning and especially to make this a compiler
error would contravene the de facto rule that catchall exception handlers
can always be coded regardless of what is thrown in the try block.
• The other change has more the look and feel of an actual bug in the javac
compiler. Here is an example adapted from Bug Id 4046575:
public class Test {
public static void main(String args[]) throws Exception{
try {
throw new Exception(); //explicit throw
} catch(SubException e) { }

try {
propagate(); //okay
} catch(SubException e) { }
}

static void propagate() throws Exception {


throw new SubException ();
}

796 JAVA RULES


}
class SubException extends Exception { }

This really should not compile. The explicitly thrown Exception is clearly
not assignable to SubException, and both are checked exceptions for
which the strictest compiler type checking should be implemented.
When the checked exception in the second bulleted item is propagated (versus
being explicitly thrown), the catch clause is reachable because any subclass
of an exception type in the throws clause of a method or constructor can
be thrown in the method or constructor body, which is just another way of say-
ing that superclass types can be named in a throws clause. This is a widely
recognized problem with the following specification for unreachable catch
clauses.
A catch block C is reachable [if and only if] both of the following are
true:

• Some expression or throw statement in the try block is


reachable and can throw an exception whose type is
assignable to the parameter of the catch clause C. (An
expression is considered reachable iff the innermost state-
ment containing it is reachable.)
• There is no earlier catch block A in the try statement
such that the type of C's parameter is the same as or a sub-
class of the type of A's parameter.120 [emphasis added]
This specification has come under such criticism that I am sure Bracha will
resolve the problem in the next edition. Instead of saying that the exception must
be “assignable” to the type of the exception-handler parameter, there has to be
some way of saying that the type of the exception-handler parameter can also be
a subclass of the exception thrown. Part of the problem is that there currently is
no terminology for this. Suggestions include “cast convertible” (The Java Spec
Report) and “castable” (IBM). I sincerely hope Bracha uses neither. A better
approach would be to formalize the definition of related classes, and then specify
that a catch clause is reachable if the type of the exception-handler parameter
is “related” to a type named in the throws clause. It could then further specify

120. Gosling et al., §14.20, “Unreachable Statements.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 797


that if the checked exception is explicitly thrown in the try block (versus being
propagated) that the type of the exception must be “assignable” to the type of
the exception-handler parameter (i.e., a widening conversion). If the exception
is explicitly thrown (as in this example), a catch clause with a subclass
type exception-handler parameter is not reachable. That is what I see as
primarily wrong with the specification for reachable catch clauses, not the
rather minor issue of using “assignable” instead of “related” or some other term.
That, after all, is only a problem in the wording of the specification.
Exception-handler parameters cannot have the same name as local variables
or parameters that are in scope. The Second Edition of the JLS introduced a
minor error when discussing the scope of exception-handler parameters. It first
correctly states that exception-handler parameters scope to their respective
catch block, and then adds the following rule.
An exception parameter of a catch clause must not have the same
name as a local variable or parameter of the method or initializer block
immediately enclosing the catch clause, or a compiler error
occurs.121

This is not entirely true, however. For example,

class Test {
public static void main(String[] args) {
try { }
catch (Exception e) { }
char e = 'e';
}
}

This program does compile showing that an exception-handler parameter can


have “the same name as a local variable or parameter of the method or initializer
block immediately enclosing the catch clause.” The original JLS stated that “an
exception parameter must not have the same name as a local variable or param-

121. Gosling et al., §14.19, “The try Statement.” This is not the only problem with the wording in
this section of the JLS. The next paragraph in the same section is very difficult to read. The “of the
directly enclosing method or initializer block” should be dropped. If such a local variable were erro-
neously declared, it would be local to the catch block anyway. On the other hand, I noticed long
ago that programmers just love to find even the most trivial of mistakes in the Java specifications.

798 JAVA RULES


eter in whose scope it is declared.”122 The “in whose scope” or an equivalent
qualifying phrase was omitted in the Second Edition. The reason why Bracha had
to update this section of the JLS was to point out that “an exception parameter
may be shadowed anywhere inside a class declaration nested within the Block of
the catch clause.”123 The same is true of local variables and method parame-
ters. Declaring a class in a catch block is somewhat unusual, but I have seen it
done at least once in the core API.

NOTE 6.1
Unless I add this note I fear the following section would give rise to the
widespread use of catchall exception handlers. That is not my intent. I ar-
gue strongly in favour of their use (when appropriate) because they have
gotten a lot of bad press. For example, The Java Tutorial has said for
many years now that catch(Exception e) is “useless”124 and The
Java Programming Language, Third Edition says this catchall ex-
ception handler is “usually a poor implementation choice.”125 Yet there
are literally hundreds of such exception handlers in the core API as well
as a significant number of catch(Throwable e) exception han-
dlers. For that reason alone they should be discussed. Perhaps the most
important thing that could be said about catchall exception handlers,
however, is that they should be used with the greatest of care.

6.8.1.1 Catchall Exception Handlers


The catch(Exception e) and catch(Throwable e) exception han-
dlers are best thought of as catchall exception handlers. This is how to
express “catch everything” or “if anything goes wrong” in code. Inside such a

122. James Gosling, Bill Joy, and Guy Steele, The Java Language Specification, First Edition,
(Reading: Addison-Wesley, 1996), §14.18, “The try statement.” (Do not update.)
123. Gosling et al., §14.19, “The try statement.”
124. Mary Campione and Kathy Walrath, “The Catch Block(s)”, java.sun.com/docs/books/
tutorial/essential/exceptions/catch.html.
125. Ken Arnold, James Gosling, and David Holmes, §8.4, “try, catch, and finally.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 799


catch block, you should implement a single exception handling strategy (which
more often than not is exception translation). Never code catch(Exception
e) or catch(Throwable e) and then use the instanceof operator to
determine what type of exception was thrown. Multiple catch clause should be
used instead.
The necessity for catchall exception handlers is simply that there is no guar-
antee that a method or constructor will not throw undocumented exceptions. As
stated in the “How to Write Doc Comments for the Javadoc Tool” document:
Since there is no way to guarantee that a call has documented all of the
unchecked exceptions that it may throw, the programmer must not
depend on the presumption that a method cannot throw any unchecked
exceptions other than those that it is documented to throw. In other
words, you should always assume that a method can throw unchecked
exceptions that are undocumented.126

This applies to undocumented runtime exceptions and errors, but undocumented


checked exceptions are also possible. For example, there is no mechanism to
check that the throws clause in a native method is accurate. Another exam-
ple is the newInstance() method in the Class class which propagates any
exception thrown in the no-argument (or default) constructor of the class being
instantiated, including checked exceptions. For example,

public class Test {


public Test() throws CheckedException {
throw new CheckedException();
}
public static void main(String args[]) {
try {
Test.class.newInstance();
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
}
}
class CheckedException extends Exception { }

126. Unascribed, “How to Write Doc Comments for the Javadoc Tool” at java.sun.com/j2se/
javadoc/writingdoccomments/index.html, “Documenting Exceptions with @throws Tag.”

800 JAVA RULES


Executing this program throws a CheckedException even though there is
no throws clause in the main method.127 There may other holes that make it
possible for methods and constructors to throw undocumented exceptions, but
these alone are sufficient to warrant the use of catchall exception handlers.
The Java programming language was designed with catch(Exception
e) in mind as a catchall exception handler. As stated in the JLS:
The class Error is a separate subclass of Throwable, distinct
from Exception in the class hierarchy, to allow programs to use the
idiom:

} catch (Exception e) {

to catch all exceptions from which recovery may be possible without


catching errors from which recovery is typically not possible.128

That the language designers thought errors should not be caught is unambigu-
ously expressed in the API docs for the Error and Exception superclasses:
An Error is a subclass of Throwable that indicates serious prob-
lems that a reasonable application should not try to catch.129 [from the
Error class]
The class Exception and its subclasses are a form of Throwable
that indicates conditions that a reasonable application might want to
catch.130 [from the Exception class]

These specifications are fundamentally flawed, however, because recovery is


not the only reason for catching an exception. In fact, exception transla-
tion is the single most important reason for using a catchall exception
handler. When translating an exception, the classification of the exception

127. This undocumented behavior was first reported on April 26, 1999 in Bug Id 4233093, the
evaluation of which states: “The documentation of Class.newInstance needs to be modified to docu-
ment the current behavior. The method will propagate any exceptions which may be thrown when
invoking the default constructor.” As of this writing, the API docs for the newInstance() method
in Class still do not document this behavior. On the other hand, Bug Id 4233093 is still marked “In
progress, bug.” Almost three years after it was submitted, there appears to be some indecision as
to the wisdom of fixing this bug.
128. Gosling et al., §11.5, “The Exception Hierarchy.”
129. API docs for the java.lang.Error class.
130. API docs for the java.lang.Exception class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 801


caught as an Error, RuntimeException, or other (checked) exception
generally does not matter. As stated above, the intent of such a catchall excep-
tion handler is to express “catch everything” or “if anything goes wrong” in code.
Nevertheless one must deal with the notion of what I like to describe as an
“error chute” when discussing catchall exception handlers. The error chute is
to an execution stack what the trash chute is to a high-rise apartment building:
open the door and toss (or rather throw) your trash (or exception) down the
chute and it falls unobstructed all the way to the bottom of the apartment build-
ing (or execution stack). This may in fact be how the exception mechanism was

The error chute is a myth. Throwing an error in an object-oriented


programming language is not the same as it was in procedural lan-
guages. It is not tantamount to invoking System.exit(1).

originally designed to work, but it is at most a programmer convention. The


error chute analogy works, but only during system initialization. Anyone familiar
with the <clinit> method for the System class can tell you that system ini-
tialization is a free-for-all. About the only rule is that anyone has the right to end
the process. They do that by throwing an error. Shutting down an application
after system initialization is the prerogative of client programmers. In an object-
oriented programming language, when packages (that are not part of the
core API loaded during system initialization) fail, they throw high-level
(checked) exceptions, not errors.
I cannot overstate the importance of realizing that exception handling on the
Java platform has evolved since this specification for the catch(Exception
e) exception handler and the API docs for the Error and Exception were
originally written. One very significant change in this regard was the deprecation

The catch(Throwable e) exception handler must be used to


bulletproof critical systems when failure is not an option.

of the overloaded stop methods in the Thread class in the 1.2 release. These
are the methods that throw ThreadDeath. That opened the way for the

802 JAVA RULES


catch(Throwable e) to replace catch(Exception e) as the catchall
exception handler of choice.
Programmers are understandably reluctant to express “catch everything” or
“if anything goes wrong” as catch(Throwable e) because of catching
“serious” errors such as an OutOfMemoryError. I will discuss this
VirtualMachineError and others momentarily. The most serious objec-
tion to the use of catchall exception handlers, however, is that they
catch runtime exceptions and assertion errors and may therefore con-
ceal programming errors. This is the reason why I say they must be used with
the greatest of care, not errors that are thrown in the production environment.
For example,

import java.lang.reflect.*;
public class Test {
public static void main(String args[]) {
try {
Class c = Class.forName("HelperClass");
Method main = c.getMethod("main",
new Class[] {args.getClass()});
main.invoke(null, new Object[] { args });
} catch (Throwable e) {
System.out.println("Hello World!"); //default message
}
}
}
class HelperClass {
public static void main(String[] args) {
System.out.println("Goodbye Cruel C++ World!");
}
}

Executing this program prints Goodbye Cruel C++ World!. Now look what
happens if I introduce a programming error by replacing the line in bold with the
following.

main.invoke(null, new Object[] { });

The main method is now being invoked with an incorrect number of arguments.
Executing the program prints "Hello World!" when it should have thrown a

ASSERTIONS, EXCEPTIONS, AND LOGGING 803


runtime exception. This is a potentially serious problem when using catchall
exception handlers. One way to avoid this problem is to code a runtime excep-
tion handler in front of the catchall exception handler and rethrow runtime excep-
tions. For example,

try {
Class c = Class.forName("HelperClass");
Method main = c.getMethod("main",
new Class[] {args.getClass()});
main.invoke(null, new Object[] { });
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
System.out.println("Hello World!"); //default message
}

This version of the code would have thrown the following runtime exception dur-
ing development and unit testing (the stack trace has been omitted).

Exception in thread "main" java.lang.IllegalArgumentException:


wrong number of arguments

The problem with this solution is that it overlooks the reality of runtime excep-
tions such as MissingResourceException, SecurityException,
and NumberFormatException that should have been declared as checked
exceptions. As a result, the catch(Throwable e) exception handler no
longer expresses “catch everything” or “if anything goes wrong.” A sepa-
rate catch(RuntimeException e) exception handler to the left of
the catchall exception handler is therefore not an option.
There must be some way of making sure that the programmer is made
aware of runtime exceptions and assertion errors during development and unit
testing so that they can be corrected. One way is simply to display the exception
to standard error. For example,

} catch (Throwable e) {
//this display is for runtime exceptions and assertion errors
//thrown during development and unit testing
e.printStackTrace();

804 JAVA RULES


System.out.println("Hello World!"); //default message
}

The only difference between this an what the default exception handler does is
that the program continues to execute. Alternatively the stack trace can be writ-
ten to a log file. Under no circumstances whatsoever should a catchall
exception handler be allowed to lose failure information. Doing so
increases the possibility that a runtime exception or assertion error could escape
detection during development and unit testing. This means that catchall exception

There are only one two rules for using catchall exception handlers:
display the exception or error caught to standard error (at
least during development and unit testing) and do not lose failure
information.

handlers should always use exception chaining when translating the exception or
error caught into a high-level (checked) exception. So long as this is done, how
and when a runtime exception or assertion error caught in a catchall exception
handler comes to the attention of the responsible programmers does not really
matter.
What about errors other than assertions. Isn’t catching a “serious” error
such as an OutOfMemoryError dangerous? There are four categories of
errors that need to be considered:
• Linkage errors
• Virtual machine errors
• System configuration errors (as defined in the introduction to this chapter)
• Assertions
Linkage errors always indicate a problem with a single class, so there is no rea-
son not to catch them. System configuration errors generally occur during sys-
tem initialization and therefore cannot caught by application programmers. Even
if they are thrown after system initialization (because of lazy initialization), a sys-
tem configuration error is like a linkage error in that it indicates a problem with

ASSERTIONS, EXCEPTIONS, AND LOGGING 805


single class. Assertions are disabled in production, so they are not a problem.
That leaves only virtual machine errors.
There are only four virtual machine errors: OutOfMemoryError, Stack-
OverflowError, InternalError, and UnknownError. Of these, the
one that is most often cited as a reason for not catching errors is OutOf-
MemoryError. Whenever I develop a coding practice based on an irrational
fear of some language construct, I describe it as superstitious programming.
OutOfMemoryError evokes this kind of fear. For example, I recall a program-
mer asking if an OutOfMemoryError thrown in a try block would preempt
the execution of a finally block. The answer is No. An OutOfMemory-
Error is not the bogyman. There are three scenarios under which this error is
thrown:
• Some programs allocate extraordinarily large objects. For example, using
Integer.MAX_VALUE as a dimension expression for an array with a
component type of long, double, or object references (such as new
Object[Integer.MAX_VALUE]) on a 32-bit architecture requires 16
gigabytes of memory. On a 64-bit architecture the same array requires 32
gigabytes of memory. Objects that large will throw an OutOfMemory-
Error on most desktop computers and workstations. Programs that allo-
cate such extraordinarily large objects should have a comprehensive excep-
tion handling strategy for an OutOfMemoryError.
• A memory leak
• A heap size that is just too small for the application
In the case of a memory leak or a heap size that is just too small, an OutOf-
MemoryError is like the grim reaper. It will not go away. Every attempt to allo-
cate memory will throw the same error until at some point it succeeds in unwind-
ing a stack. (The JVM pre-allocates an instance of the OutOfMemoryError
class for this very reason.) Why should such an error be allowed to dictate
your exception handling strategy? At most a catch(Throwable e)
exception handler is going to momentarily delay the inevitable.
StackOverFlowError is either thrown in a deeply recursive algorithm
that requires a larger stack or else because of an infinite loop. In either case,

806 JAVA RULES


merely throwing this error solves the immediate problem, so there is no danger
in catching a StackOverflowError.
The poorly documented UnknownError and InternalError excep-
tion classes are VirtualMachineError subclasses that sound a lot like a
JVM crash is in progress. In fact, the API docs (an interface contract with client
programmers) state that VirtualMachineError is “thrown to indicate that
the Java Virtual Machine is broken or has run out of resources necessary for
it to continue operating.”131 [emphasis added] And according to the JVMS,
InternalError is thrown “because of a fault in the software implement-
ing the virtual machine, a fault in the underlying host system software, or
a fault in the hardware.”132 If either the API docs for the Virtual-
MachineError class or the JVMS are correct in this regard, then catching
either UnknownError or InternalError is potentially disastrous.
Because their relevance to catchall exception handlers, every Java program-
mer should understand something of the history of UnknownError and
InternalError. Though the latter is a VirtualMachineError, it is
often thrown in clone() method implementations. The practice of throwing
InternalError in clone() method implementation predates the 1.4
release in which assertions were introduced to the Java programming language.
As argued in 6.2.3 Catching CloneNotSupportedException, assert
false should now be used instead. This is only the tip of the iceberg, however.
The more important issue is to understand is how these errors were intended to
be used in the 1.0 release.
Though never documented, my understanding is that the Classic VM basi-
cally threw three kinds of exceptions:
• Specified Exceptions and Errors: Exceptions and errors specified in either
the JLS or JVMS such as IndexOutOfBoundsException, Null-
PointerExeception, ClassCastException, Arithmetic-
Exception, OutOfMemoryError, StackOverflowError, etc.
The operative word here is “specified.” Either the JLS or the JVMS specifies

131. API docs for the java.lang.VirtualMachineError class.


132. Tim Lindholm and Frank Yellin, §2.16.4, “The Classes Exception and RuntimeException.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 807


both the cause of these exceptions and errors as well as the precise point
at which they are thrown.
• Unknown Errors: The API docs for the UnknownError class in the 1.0.2
release stated that “the Java runtime throws an instance of this class when
an unknown but serious exception has occurred.”133 This was changed to
read “thrown when an unknown but serious exception has occurred in the
Java Virtual Machine”134 in the 1.1 release and has remained the same ever
since. I spent a long time researching this error in forums and in the Bug
Database. The problem was apparently a very poor design of the Java VM in
which native code could throw misspelled exception class names which
were then translated into UnknownError. The classloader subsystem
was plagued by a number of such bug reports (always showing load-
ClassInternal at the top of the stack) before a significant rewrite in
the 1.2 release. See for example Bug Id 4054295 in which Class-
FormatException is mistakenly used in native code where Class-
FormatError was intended. This same kind of “typo” mistake when
throwing an exception in the Classic VM is documented in Bug Ids 4024579
(which names some of the relevant VM function names responsible for these
errors) and 40280284. The HotSpot VM does not throw Unknown-
Error, but it is possible that some browsers and third-party JVM still
thrown this error.
• Internal Errors: Contrary to the API docs and JVMS (quoted above)
this error is not and never has been a JVM crash. In fact, I have only
been able to find a handful of bug reports in which the JVM is documented
as having thrown an InternalError. (Of course, this depends on your
definition of what constitutes the JVM.) Most of them were in the 1.0 release
of the Classic VM. For example,
• Bug Id’s 4016967, 4031122, and 4040190:
Internal-Error was briefly used (in some 1.1 releases)
if the Classic VM could not allocate a large enough stack
frame
• Bug Id 4065025: A method with 5,000 local variables
“compiles fine, no warnings, no errors. But if you try and run
it, you get an InternalError”135

133. API docs for the java.lang.UnknownError class in the 1.0.2 release.
134. API docs for the java.lang.UnknownError class in the 1.1 release.
135. Description of Bug Id 4065025.

808 JAVA RULES


It is notable that both of these errors represent the failure to execute a sin-
gle method, in which case using the exception mechanism is safe. I am sure
there are other examples of throwing InternalError in the Classic VM,
but as far as I know none that are still thrown in the HotSpot VM.
Both InternalError and UnknownError suffer from the same problem in
that they are part of the Classic VM design, not the HotSpot VM. Furthermore, it
is doubtful that any VM would use them as they were originally intended.
The need for an UnknownError in the Classic VM stemmed from a very poor
design. Likewise, if the JVM were really “broken”131 or if there really were “a fault
in the software implementing the virtual machine, a fault in the underlying host
system software, or a fault in the hardware,”132 throwing an InternalError
is a seriously flawed design. It is inherently unsafe. A good analogy would be
trying to safely land a fighter jet that is in flames and about to explode rather
than using the ejector seat. The main problem is that throwing an Internal-
Error in a multithreaded application at most is going to unwind a single thread.
It will not abort the JVM.
What then is a JVM crash if not the throwing of an InternalError? While
I have never actually experienced one, when the JVM crashes it displays a cryp-
tic message to standard error. In the Classic VM that message looked like this:

SIGSEGV 11* segmentation violation


si_signo [11]: SIGSEGV 11* segmentation violation
si_errno [0]: Error 0
si_code [1]: SEGV_ACCERR [addr: 0xee076000]
stackbase=EE0A2000, stackpointer=EE09D210

This would be followed by a full thread dump. In the HotSpot VM a crash looks
like this:

#
# HotSpot Virtual Machine Error, Internal Error
# Please report this error at
# http://java.sun.com/cgi-bin/bugreport.cgi
#
# Error ID: 53414645504F494E540E43505002EA
#

ASSERTIONS, EXCEPTIONS, AND LOGGING 809


I believe the “Error ID” decodes to a particular line within a .cpp source code
file. The HotSpot VM crash format has expropriated the terminology of an
InternalError. This is not an InternalError, however. Notice how
“Internal Error” is two words. This is how you can tell the difference when
searching the Bug Database or forums for information about InternalError
versus a HotSpot VM crash.
I ran across a very thoughtful reply to a question about a “HotSpot Virtual
Machine Error” (which one naturally assumes to be a JVM crash) posted to a
Java forum, and would be remiss not to share it here:
You have to be careful with messages like “HotSpot Virtual Machine
Error”, which may lead you to believe that the Hotspot VM itself encoun-
tered an irrecoverable situation all by its very own doing. More often
than not, in my experience, the source of such problems is really in the
user's (programmer's) native code (i.e., called via JNI), that of a 3rd
party (e.g, a servlet engine or JDBC driver), or somewhere in the OS
itself (which can often be rectified by applying latest OS patches).

I (and several others) have written many replies to such posts because
I have dealt with my share of errors that were manifested as “HotSpot
Virtual Machine Error” written to stderr, and the source of such errors
was actually in the programmer's own native code. That doesn't mean
that actual JVM bugs don't exist, it just suggests that the probability of
a given problem manifesting itself in this way actually turning out to be
a JVM bug is low compared to the other sources mentioned.136

Thanks to “Chris” for these words of wisdom. The 1.3.1 release introduced a
solution to the problem of differentiating native code failures from a JVM
crash. Here is an example of a native code failure from the “Error Han-
dling”137 document:

An unexpected exception has been detected in native code outside the VM.
Unexpected Signal : EXCEPTION_ACCESS_VIOLATION occurred at PC=0xe6a1067

136. “Chris”, from a Java Forum discussion at forum.java.sun.com/


thread.jsp?forum=27&thread=211184.
137. Unascribed, “Error Handling” in the API docs for the 1.4.1 release, (Mountain View: Sun Micro-
systems, 2001). Also available online at java.sun.com/j2se/1.4.1/docs/guide/vm/
error-handling.html.

810 JAVA RULES


Function name=Java_NativeSEGV_foo
Library=D:\testcases\NativeSEGV\NativeSEGV.dll
Source file = NativeSEGV.c : 7

Current Java thread:


at NativeSEGV.foo(Native Method)
at NativeSEGV.main(NativeSEGV.java:26)

Dynamic libraries:
0x00400000 - 0x00405000 d:\jdk1.3.1\bin\java.exe
0x77F60000 - 0x77FBE000 C:\WINNT\System32\ntdll.dll
0x77DC0000 - 0x77DFF000 C:\WINNT\system32\ADVAPI32.dll
0x77F00000 - 0x77F5E000 C:\WINNT\system32\KERNEL32.dll
0x77E70000 - 0x77EC4000 C:\WINNT\system32\USER32.dll
0x77ED0000 - 0x77EFC000 C:\WINNT\system32\GDI32.dll
0x77E10000 - 0x77E67000 C:\WINNT\system32\RPCRT4.dll
0x78000000 - 0x78040000 C:\WINNT\system32\MSVCRT.dll
0x10000000 - 0x10018000 C:\WINNT\System32\NVDESK32.DLL
0x08000000 - 0x08273000 d:\jdk1.3.1\jre\bin\server\jvm.dll
0x77FD0000 - 0x77FFA000 C:\WINNT\System32\WINMM.dll
0x4FA00000 - 0x4FA77000 C:\WINNT\System32\cwcmmsys.dll
0x50220000 - 0x50227000 d:\jdk1.3.1\jre\bin\hpi.dll
0x503B0000 - 0x503BD000 d:\jdk1.3.1\jre\bin\verify.dll
0x50250000 - 0x50266000 d:\jdk1.3.1\jre\bin\java.dll
0x503C0000 - 0x503CD000 d:\jdk1.3.1\jre\bin\zip.dll
0x0E6A0000 - 0x0E6C2000 D:\testcases\NativeSEGV\NativeSEGV.dll
0x77920000 - 0x77942000 C:\WINNT\System32\imagehlp.dll
0x72A00000 - 0x72A2D000 C:\WINNT\System32\DBGHELP.dll
0x731B0000 - 0x731BA000 C:\WINNT\System32\PSAPI.DLL

Local Time = Fri Dec 15 17:14:02 2000


Elapsed Time = 1
#
# The exception above was detected in native code outside the VM
#
# Java VM: Java HotSpot(TM) Server VM (internal-release mixed mode)
#
# An error report file has been saved as hs_err_pid102.log.
# Please refer to the file for further information.
#

ASSERTIONS, EXCEPTIONS, AND LOGGING 811


The documentation refers to this as a error-handling mechanism , which is a
little confusing. It has nothing to do with the catch clause (or exception han-
dler) in a try statement.
The fact that UnknownError is no longer in use and InternalError
is not a JVM crash is very significant because these are the last of the errors in
the core API that could present a problem with using the catch(Throwable
e) exception handler. The point of this exercise has been to quell any fears that
catch(Throwable e) will ensnare a giant squid, fire-breathing dragon, or
some other predatory creature that will eat your code for lunch.
The catch(Throwable e) exception handler is not only safe, but I
believe it will become the catchall exception handler of choice. The remainder of
this section takes a very close look at one of the most common exception han-
dling problems is all of the core API in order to make the point that
catch(Throwable e) is a better choice than catch(Exception e).
Before doing so, however, I would like for you to read the following quote from
one of those really great Bill Venner interviews with Java luminaries.
…one of the things that people do a lot in Java if they are building a
command dispatch loop, and it's invoking things that are plugged in, is
wrap that call with a try catch Throwable. That will catch all
kinds of errors, even the ones that are contract violations. That can be
a really great way to increase the reliability of the system, because it
gives you a way of saying, “Okay, this piece of the system just died hor-
ribly because it had a contract violation, but damn it, I've got to carry
on.”

…in systems that have to be long lived and reliable, they [the systems]
have to have a comprehensive strategy for dealing with failure,
because failure always happens. There will always be bugs, there will
always be pieces of equipment that get smacked. There will always be
alpha particles that hit busses. Things go weird and the average
answer, which is to just roll over and die, is not a useful one. And partic-
ularly as you get into the systems like flight avionics, you just don't get
to crash…you have to keep on going, you have to do something sensi-
ble. You can't just say, “Oops, no, I'm not going to work anymore.” 138

812 JAVA RULES


This is a strong endorsement of expressing “catch everything” or “if anything
goes wrong” in code as catch(Throwable e) from no less than Dr. Gos-
ling, the Father of Java.
One of the most common exception handling problems in all of the core API
is loading and instantiating a class designated by the value of a system
property. There are a total of five exceptions thrown when solving this problem
(three checked and two runtime exceptions). The same problem is solved over
and over again in core API, so it is interesting to look at the differences in how
these exceptions are catch and handled.
About the only difference from one solution to the next is which class loader
is used139 and how the exceptions are handled. When invoking Class.for-
Name(String className), the class is loaded using the same class
loader as was used to load the current class. If the current class is part of the
core API (which is always the case in the examples below), that means the boot-
strap (or system) class loader is used. Otherwise, if the loadClass(String
name) method in ClassLoader is used, the same class loader is used. Here
again, in the examples below this is always the bootstrap (or system) class
loader. Regardless of how the class is loaded, a ClassNotFound-
Exception can be thrown. After the class is loaded, the newInstance() in
the Class class must be invoked to instantiate the class. This methods throws
IllegalAccessException and InstantiationException. Those
are the three checked exceptions. In addition, a ClassCastException can
be thrown if for some reason the wrong type of class is stored in the system
property. Finally, though undocumented both the overloaded loadClass meth-
ods in ClassLoader and the overloaded Class.forName methods in
Class throw NullPointerException if null is passed instead of a
class name. This can happen if System.getProperty(String key)
returns null because there is no property for a given key and the return value
is not checked.

138. Dr. James Gosling in a Bill Venner’s interview, the full text of which is available on the
www.artima.com/intv/gosling310.html. One must assume that Dr. Gosling was being sar-
castic when referring to a “crash” in a flight avionics system.
139.

ASSERTIONS, EXCEPTIONS, AND LOGGING 813


Now we are ready to look at some of the different solutions to this problem.
Here is an example that catches four of the five exceptions that can possibly be
thrown when loading and instantiating a class designated by the value of a sys-
tem property:

String s = System.getProperty("java.security.manager");
SecurityManager sm = null;
if (name != null)
if (s.equals("") || s.equals("default") {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}

A try block such as this is used by the java launcher to load the
SecurityManager. This is an interesting example not only because the
responsible programmer has taken the time to code an exception handler for all
but one of the known checked and runtime exceptions, but also because he or
she has found of way of coding the exception handling strategy (which is to
throw an InternalError) only once. That becomes an issue if you are going
to code multiple catch clauses for what is essentially the same exception.
For example, here is essentially the same code from the PrinterJob class in
the java.awt.print package.

String s = System.getProperty("java.awt.printerjob", null);


try {
return (PrinterJob)Class.forName(s).newInstance();

814 JAVA RULES


} catch (ClassNotFoundException e) {
throw new AWTError("PrinterJob not found: " + s);
} catch (InstantiationException e) {
throw new AWTError("Could not instantiate PrinterJob: " + s);
} catch (IllegalAccessException e) {
throw new AWTError("Could not access PrinterJob: " + s);
}

As is often the case when this problem is solved in the core API, the possibility of
a ClassCastException is either deliberately or inadvertently overlooked.
There is also usually no catch clause for NullPointerException, which
is notable in this example because the responsible programmer has specified
null as the default value to be returned by the System.getProperty
method and still does not check the return value. Handling these runtime excep-
tions would have required two more exception handlers. The immediate point,
however, is that the exception handling strategy (which in this case is to throw
an AWTError) has to be coded over and over again.
Most programmers simply will not code multiple catch clauses for what is
essentially the same exception, nor should they. It would be incorrect to charac-
terize this as lazy programming. Five exception handlers for what is essentially
the same exception is as messy as checking the return value of every method
invoked (as was done in the C programming language). It is a step backwards in
terms of the design goals of the exception mechanism in the Java programming
language. The only solution to this problem is to use a catchall exception handler.
The Preferences class in java.util.prefs does just that, using
the catch(Exception e) exception handler:

String factoryName =
System.getProperty("java.util.prefs.PreferencesFactory");
if (factoryName == null)
throw new InternalError(
"System property java.util.prefs.PreferencesFactory not set");
try {
factory = (PreferencesFactory)
Class.forName(factoryName, false,
ClassLoader.getSystemClassLoader()).newInstance();
} catch(Exception e) {
throw new InternalError(

ASSERTIONS, EXCEPTIONS, AND LOGGING 815


"Can't instantiate Preferences factory " + e);
}

Not that the intent here must be characterized not as “catch everything” or “if
anything goes wrong,” but as “catch all known exceptions, but not errors.” This
is an entirely different motivation than catch(Throwable e).
Primarily because the newInstance() method propagates checked
exceptions that are not named in the throws clause, I would take the
Preferences exception handling strategy a step further and make the code
as compact as possible. For example,

public Class instantiate(String key) {


String className = System.getProperty(key);
try {
return Class.forName(className).newInstance();
} catch (Throwable e) {
throw new SystemConfigurationError(
"Cannot instantiate " + className, e);
}
}

Why not catch linkage errors such as ExceptionInInitializerError in


a situation such as this? How are they substantially different from any of the
other five exceptions that would prevent this class from being used? This solu-
tion makes it much easier to discern the intent of the programmer. It very clearly
says “If anything goes wrong throw a SystemConfigurationError.” The
implication is that the responsible programmer fully expects this code to work.
This exact same exception handling strategy is valid for almost every use of
the reflection API when loading and instantiating a class designated by a system
property. So what if you also invoke a method or constructor on the class instan-
tiated. For there to be a NoSuchMethodException for a known method or
constructor signature is essentially the same SystenConfiguration-
Error as a ClassNotFoundException. The same can be said for a
NoSuchFieldException . Inasmuch as there is nothing you can do about

816 JAVA RULES


an InvocationTargetException,140 there is no reason to handle it sepa-
rately. Indeed, it rarely if ever is handled separately. If core API programmers
were to agree to this line of reasoning it would eliminate many hundreds if not
thousands of lines of code from the core API, and I would venture to say make
for a more stable platform. Moreover, there should be some consistency to the
exception handling strategies employed in the core API.

6.8.2 The finally Clause


The design of the Java programming language is that once control passes into a
try block, if there is a finally block it is always executed immediately
before control is transferred back out of the try statement. Nothing pre-
empts the execution of a finally block. As stated in the JLS:
In situations where it is desirable to ensure that one block of code is
always executed after another, even if that other block of code com-
pletes abruptly, a try statement with a finally clause may be
used.141

Abrupt completion is defined as executing any of the four control-transfer state-


ments: break, continue, return, and throw. All four are executed in the
following try-catch-finally statement.

class Test {
public static void main(String[] args) {
for (int i=0; i < 4; i++)
test(i);
}
static void test(int i) {

140. For someone contemplating the use of catch(Throwable e) as the catchall exception
handler of choice, it is notable that this is precisely what the newInstance(Object[]
initargs) method in Constructor and the invoke(Object obj, Object[] args)
method in Method does. Any exception—including runtime exceptions and errors—thrown in a
method or constructor (other than the newInstance() method in Class) invoked using the
reflection API is caught and translated into an InvocationTargetException. That this was
not done for the newInstance() method in Class was a major API design oversight. Alas, you
can’t please everyone. Bug Id 4386935 complains that InvocationTargetException
should not catch runtime exceptions and errors (using the threadbare argument of “What happens if
an OutOfMemoryError is thrown?”). This one, however, is marked “Closed, will not be fixed” (as
rightly it should be).
141. Gosling et al., §11.3, “Handling of an Exception.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 817


boolean looping = true; //for continue statement
loop: while (looping) {
try {
looping = false;
switch (i) {
case 0: break loop;
case 1: continue loop;
case 2: return;
case 3: throw new Exception();
}
} catch (Exception e) {
System.err.println(e);
//System.exit(1);
} finally {
System.out.println("finally block");
}
}
}
}

Executing this program prints

finally block
finally block
finally block
java.lang.Exception
finally block

There is actually one way out of a try statement without executing the
finally block (short of unplugging your computer). If System.exit(1) in
the catch block is uncommented, the last finally block would not print.

6.8.2.1 try-finally as a Control-flow Statement


A try-finally statement can serve a purpose not directly related to excep-
tion handling. For normal completion, the effect is the same as removing the
try statement and appending the code in the finally block to the code in
the try block. In the case of abrupt completion, however, the finally block
is “guaranteed” to execute. There are an increasing number of specifications
that require the use of try-finally statements to “guarantee” the execution
of some method. It simply does not matter if the try block throws an

818 JAVA RULES


exception. The API designers who write such specifications are only interested
in the guarantee that the finally block will be executed. For example, here is
the specification for the readUnlock() method in the
AbstractDocument class in javax.swing.text:
Does a read unlock. This signals that one of the readers is done. If
there are no more readers then writing can begin again. This should be
balanced with a readLock, and should occur in a finally state-
ment so that the balance is guaranteed. The following is an example.

readLock();
try {
// do something
} finally {
readUnlock();
}142

In the 1.4 release there are two classes in java.nio.channels.spi that


have similar specifications. AbstractInterruptibleChannel is one of
those two classes. It includes the following specification.
This class encapsulates the low-level machinery required to implement
the asynchronous closing and interruption of channels. A concrete
channel class must invoke the begin and end methods before and
after, respectively, invoking an I/O operation that might block indefi-
nitely. In order to ensure that the end method is always invoked, these
methods should be used within a try … finally block:

boolean completed = false;


try {
begin();
completed = …; // Perform blocking I/O operation
return …; // Return result
} finally {
end(completed);
}

The completed argument to the end method tells whether or not


the I/O operation actually completed, that is, whether it had any effect

142. API docs for the readUnlock() method in the javax.swing.text.Abstract-


Document class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 819


that would be visible to the invoker. In the case of an operation that
reads bytes, for example, this argument should be true if, and only if,
some bytes were actually transferred into the invoker's target
buffer.143

The end(boolean completed) checks to make sure the channel was not
closed during the operation, throwing an AsynchronousCloseException
if it was. (This is roughly equivalent to a fail-fast iterator in the Collections Frame-
work.) There is a similar specification for the end() and begin() methods in
the AbstractSelector class of the same package.
It is easy to see how such specifications could proliferate, which is why it is
important to fully understand the raison d’être for try-finally statements in
which no exceptions are thrown. Such try-finally statements are best
thought of as control-flow statements. I did not fully appreciate the fact that
try-finally is actually a control-flow statement until I stumbled across the
following example from the RawCommandLineLauncher class in the
com.sun.tools.jdi package.

try {
return launch(tokenizeCommand(command, quote.charAt(0)),
address);
} finally {
transportService().stopListening(address);
}

This try-finally statement should get your attention. The first time I saw
this code I thought it must be a mistake. Then it occurred to me that this is a
clever try-finally trick that allows invocation of the stopListen-
ing(String address) method to be postponed to the last possible milli-
second. The “trick” is that the return statement is fully evaluated before
the finally block is executed. For example,

class Test {
public static void main(String[] args) throws Exception {
test();

143. API docs for the java.nio.channels.spi.AbstractInterruptibleChannel


class.

820 JAVA RULES


}
static String test() {
String s = null;
try {
return (s = "fully evaluated");
}
finally {
System.out.println(s);
}
}
}

Executing this program prints fully evaluated. Assuming that the


finally block completes normally, there is little or no chance that the
method will fail to return after the finally block is executed. That
makes this a very interesting construct.
I fault the JLS a couple of times in this section. This is one of them. The JLS
should explicitly state that the expression is a throw or return statement
executed either in the try block or in a catch block is fully evaluated before
the finally block is executed.
The following try-finally statement from a java.net class shows
very clearly how control flow problems can be solved by using a try-
finally statement.

try {
FileDescriptor fd = acquireFD();
try {

…code to connect socket omitted…

} finally {
releaseFD();
}
} catch (IOException e) {
close();
throw e;
}

Besides “balancing” the acquireFD() and releaseFD() methods, the way


this class is designed, the releaseFD() method must be invoked before the

ASSERTIONS, EXCEPTIONS, AND LOGGING 821


close() method. Here is another example from the getDefaultTool-
kit() method in the java.awt.Toolkit class:

if (toolkit == null) {
try {
// We disable the JIT during toolkit initialization.
// This tends to touch lots of classes that aren't
// needed again later and therefore JITing is counter-
// productiive.
java.lang.Compiler.disable();

…lots of code omitted…

} finally {
// Make sure to always re-enable the JIT.
java.lang.Compiler.enable();
}
}
return toolkit;

I like this example because it is very straightforward. The JIT compiler is dis-
abled while a bunch of classes for which optimization is a waste of time are
loaded. The finally clause is used to make sure the JIT is re-enabled. The
javax.swing.UIManager class does the same thing during UI initializa-
tion. Here is another very straightforward example of a finally clause (this
time in a try-catch-finally statement) that is also completely unrelated
to exception handling.

if (!(obj instanceof Serializable)) {


return false;
}

try {
stream.writeObject(obj);
} catch (IOException e) {
return false;
} finally {
// Fix for 4503661.
// Reset the stream so that it doesn't keep a reference
// to the written object.
try {
stream.reset();

822 JAVA RULES


} catch (IOException e) {
// Ignore the exception.
}
}
return true;

This code is from a utility method used by the serialization mechanism to make
sure that a Serializable object can be serialized without throwing an
exception. The finally clause was added to reset the ObjectOutput-
Stream so that it does not hold a reference to the object. Otherwise, the object
cannot be garbage collected and becomes what is sometimes referred to as a
zombie. What all of these examples have in common is that the rely on the fact
that finally block is “guaranteed” to execute.
Why is that guarantee so important? It is easy to assume that unexpected
runtime exceptions and errors are not caught and always result in abnormal pro-
gram termination. If that were the case (“daFei” whoever you are, I hope
you are listening144), the finally clause would not be needed because
resource leaks or other problems such as no JIT compiler in a program
that is about to terminate are hardly a problem, but assumptions are never
good in computer programming. What if the unexpected runtime exception or
error is caught and the program does not terminate? This is why you can expect
to see more specifications such as these in which an API designer requires the
use of a try-finally statement to “guarantee” execution of some method.
They allow for the possibility that an unexpected runtime exception or error
thrown during execution of the try block will be caught elsewhere in the pro-
gram.

6.8.2.2 Releasing System Resources


The traditional use of a finally block which is to release system resources
(sometimes referred to as external resources). The most common system

144. I only read about half of the replies (575 as of this writing) to the now infamous daFei “Finally”
thread at forum.java.sun.com/thread.jsp?forum=31&thread=252665, but I wonder if
anyone else in the hundreds of replies I did not read began to suspect as I did that his real motive
was to achieve fame through having the most replies ever to a Java Forum posting. I got to laughing
so hard thinking that was the case that I just couldn’t read any more. He has been called many
names, but I like to think of daFei as “the idiot who everyone loves to hate.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 823


resources are file descriptors (files and sockets), graphics contexts (a
Graphics object), and memory. System resources are limited and there-
fore should be released as soon as possible. Failure to release these system
resources is referred to as a resource leak.
Before discussing this use of the finally clause it is necessary to
address the use of finalizers (the finalize() method in the Object class)
to release system resources. One of the big lessons that was learned in the
early releases of the Java programming language is that finalizers are not
dependable. This is particularly true of output streams that need to be flushed
because finalizers may not run at all. At best finalizers unnecessarily postpone
the release of system resources until the objects that reference them are gar-
bage collected. The API docs for the dispose() method does a good job of
explaining that invoking dispose() in a finally block as opposed to a final-
izer is a matter of releasing system resources sooner rather than later:
When a Java program runs, a large number of Graphics objects can
be created within a short time frame. Although the finalization process
of the garbage collector also disposes of the same system resources,
it is preferable to manually free the associated resources by calling this
method rather than to rely on a finalization process which may not run
to completion for a long period of time.145

It is just not good programming practice to wait until finalizers run to release sys-
tem resources.
Why then are there finalizers in the Graphics, FileInputStream, and
FileOutputStream classes?146 There are two possible answers to this
question. The first is that had they the opportunity to do it all over again, these
classes would not have finalizers. The attitude towards finalizers has changed
considerably over the years. For example, here is the evaluation of Bug Id
1244595, “BufferedOutputStream does not flush even when the pro-
gram exits” (dated April 4, 1996 and reported against the 1.0 FCS release):

145. API docs for the dispose() in the java.awt.Graphics class.


146. I have no idea why FileInputStream and FileOutputStream have finalizers and
RandomAccessFile does not. There are few designs I have tried harder to scrutinize, but I just
can’t think of a reasonable explanation for this design decision.

824 JAVA RULES


If you want streams to be flushed at exit, use finalizers and enable final-
ization on exit.147

Compare that to the following evaluation of Bug Id 4192722, “java.io.{


FileWriter,OutputStreamWriter} should override finalize to call
close” written over two and a half years later:
Will not fix. Finalization should not be used to manage finite resources
external to the Java heap. Making this change to existing classes also
risks breaking binary compatibility.148

If for some reason a finally clause cannot be used to close a buffered out-
put stream that must be flushed, a shutdown hook should be used instead.
The other answer to this question is that finalizers are, as one software engi-
neer at Sun has so eloquently said, “the last bastion of reclaiming system
resources.”149 This is a “better late than never” explanation for the use of finaliz-
ers in the Graphics, FileInputStream, and FileOutputStream
classes.
In the case of FileInputStream and FileOutputStream, however,
the finalizers were a blatant mistake. If two streams are using the same file
descriptor closing one of them in a finalizer will cause the other to throw an
IOException with a “Bad file descriptor” detail message.150 For example,

import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("test.txt",
"rw");
BufferedReader in = new BufferedReader(
new InputStreamReader(
new FileInputStream(raf.getFD())));
System.out.println(in.readLine());
System.out.println(raf.read());

147. Evaluation of Bug Id 1244595.


148. Evaluation of Bug Id 4192722.
149. Evaluation of Bug Id 4034630
150. I had to run the program using a 1.2 release (1.2.2_014 to be precise) in order to get the cor-
rect detail message. The 1.3 and 1.4 releases for the Windows operating system are throwing a
IOException with a “Cannot create a file when that file already exists” detail message (from
within the same native method as is shown in the above stack trace). This is an obvious bug.

ASSERTIONS, EXCEPTIONS, AND LOGGING 825


raf.close();
System.out.println(in.readLine());
System.out.println(in.readLine());
}
}

The test.txt file looks like this:

testing,
testing,
testing

Executing this program prints

testing,
-1
testing,
Exception in thread "main" java.io.IOException: Bad file
descriptor
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:161)
at java.io.InputStreamReader.fill(InputStreamReader.java:156)
at java.io.InputStreamReader.read(InputStreamReader.java:232)
at java.io.BufferedReader.fill(BufferedReader.java:130)
at java.io.BufferedReader.readLine(BufferedReader.java:267)
at java.io.BufferedReader.readLine(BufferedReader.java:322)
at Test.main(Test.java:12)

The first readLine() method invocation prints testing, as expected. The


raf.read() prints -1 because all of the data in the file has already been read
into the internal buffer of the other input stream. The second readLine()
method also prints testing, as expected (reading the data from an internal
buffer). The third readLine() method invocation empties the internal buffer
and then tries to read more data from the file. It then throws an IOException
with a detail message of “Bad file descriptor” because raf.close() released
the file descriptor. When file descriptors are duplicated (as in this example), the
close() method can only be invoked once. This is why invoking close() in
the finalizers of the FileInputStream and FileOutputStream classes
is a blatant mistake. It requires that a reference to other streams using the same
file descriptor be saved so that the file descriptor is not inadvertently released

826 JAVA RULES


by the garbage collector. This problem is addressed in Bug Id 4099999 which is
marked “In progress, bug” but was submitted on December 17, 1997.151 Obvi-
ously there is no intention to fix this bug.
System resources should be released in a finally clause (as opposed to
a finalizer), but what exactly does “releasing system resources” mean? For appli-
cation programmers it usually means nothing more than invoking a close() or
dispose() method in the core API that does the actual work of releasing the
system resources. Easily the two most common examples of this is closing a file
and disposing of a Graphics object. The remainder of this section is largely a
detailed discussion of closing files after which setting reference type variables to
null, the specification for closing streams more than once, disposing of graph-
ics objects, and releasing the memory allocated by native methods are briefly
discussed. Important parts of the close() method discussion can be easily
adapted to invoking the close() methods in java.net (closing sockets),
java.sql (closing database connections), or any of the nine other packages
in the core API that have close() methods. The reason for discussing the
close() methods in the java.io and java.nio packages (closing files) in
such detail is simply that they are the most widely used.
The java.nio package uses FileInputStream, FileOutput-
Stream, and RandomAccessFile in the java.io package to open and
close files. As stated in the API docs for the FileChannel class:
The view of a file provided by an instance of this class is guaranteed to
be consistent with other views of the same file provided by other
instances in the same program….

This class does not define methods for opening existing files or for cre-
ating new ones; such methods may be added in a future release. In this
release a file channel can be obtained from an existing FileInput-
Stream, FileOutputStream, or RandomAccessFile object
by invoking that object's getChannel method, which returns a file
channel that is connected to the same underlying file.152

151. See also Bug Id 4081750, “java.io.RandomAccessFile: Add a finalize method” which is (rightly)
marked “Closed, will not be fixed” (because of Bug Id 4099999).
152. API docs for the java.nio.channels.FileChannel class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 827


Thus a FileChannel is merely a “view” of FileInputStream, File-
OutputStream, or RandomAccessFile. Contrary to the examples in the
“New I/O APIs” document, closing a FileChannel does not close the
source or destination file. Those examples include code such as the following.

// Open the file and then get a channel from the stream
FileInputStream fis = new FileInputStream(f);
FileChannel fc = fis.getChannel();

// Close the channel and the stream
fc.close();

This code should actually invoke fis.close(), which would in fact close both
the channel and the stream. This code example is all the more curious because
an end-of-line comment in the source code for the FileChannelImpl class
clearly states that FileInputStream, FileOutputStream, and
RandomAccessFile are “responsible for closing file descriptor.”153 For
example,

import java.io.*;
import java.nio.channels.FileChannel;
class Test {
public static void main(String[] args) throws IOException {
FileOutputStream file = new FileOutputStream("output.txt");
FileChannel channel = file.getChannel();
channel.close();
PrintWriter writer = new PrintWriter(file);
writer.println("testing, testing, testing");
writer.close();
}
}

This program compiles and writes testing, testing, testing to the


output.txt file, showing that closing a channel does not close the corre-
sponding file. It is therefore important to save a reference to the FileInput-
Stream , FileOutputStream, or RandomAccessFile used to invoke
the getChannel() method (as in this example). Otherwise, the file

153. End-of-line comment in the implCloseChannel() method of the


sun.nio.ch.FileChannelImpl class.

828 JAVA RULES


descriptor cannot be released (except possibly as the result of finalization).
The remainder of this discussion therefore focuses on the close() methods in
the java.io package. Reading from or writing to a file using a “channel”
instead of a “stream” does not change how these close() methods are
invoked.
Why bother closing files if they are automatically closed by the oper-
ating system when a program exits? This is true of all operating systems,
making it all the more important to provide a thorough and detailed answer to
this question. There are two reason why files should be explicitly closed:
• The Java platform throws an IOException with a detail message of “Too
many open files” if files are not closed on a host operating system that limits
the number of open files. This limit is discussed in terms of the number file
descriptors that can be allocated by a single process; hence, a failure to
close files is referred to as a file descriptor or fd leak (and less commonly
as an open files leak).
• The design of the java.io package is that internal buffers154 are flushed
whenever they are full and again when the stream is closed. Failure to either
explicitly flush() or close() a buffered output stream after the last
write will indeed leave unwritten data in an internal buffer (except perhaps if
automatic flushing is on, but that only applies to the PrintStream and
PrintWriter classes).
Each of these reasons for explicitly closing a file is discussed at length in this
section. The rationale for doing so is again that closing files is the single most
common use of the finally clause, one to which programmers at every level
of experience can easily relate.
There is a class named FileDescriptor in the java.io package that
must be thoroughly explained in order to fully understand the “Too many open
files” detail message. A file descriptor (or open file descriptor)—otherwise
known as an FD, file handle, or handle155 —is an index value (a non-negative

154. The term internal buffer is generally not used to describe CharBuffer, ByteBuffer,
and other buffer classes in the java.nio package (at least not in the API docs), only the internal
buffer arrays used in the java.io package. Note also that there are no flush() methods in
the java.nio package. Those buffers are passed as arguments to encoders and channels
rather than being flushed.

ASSERTIONS, EXCEPTIONS, AND LOGGING 829


integer) used to access the file descriptor table in the kernel of a host operat-
ing system.156 File descriptors are always referred to as handles (or file
handles) on the Windows operating system. Note also that when a file
descriptor is allocated for an open socket, it is sometimes referred to as a
socket descriptor. Every executable program (usually referred to as a process
in this context) has a file descriptor table. Each entry in the table represents an
open file, open socket, or device such as the console or keyboard. (The first
three entries in a file descriptor table are standard input, standard output, and
standard error. Consequently, the numeric value of their file descriptors is 0, 1,
and 2, respectively) As stated in the API docs for FileDescriptor class:
Instances of the file descriptor class serve as an opaque handle to the
underlying machine-specific structure representing an open file, an
open socket, or another source or sink of bytes. The main practical
use for a file descriptor is to create a FileInputStream or File-
OutputStream to contain it.157

On the Java platform, it is not possible to determine the numeric value of a file
descriptor (other than those for standard I/O), which I believe is why the API docs
refer to file descriptors “opaque handles.” I do not agree with this characteriza-
tion of “the main practical use of a file descriptor.” Invoking the sync() method
for output streams is just as common if not more so.
File names are only used when opening files. Thereafter the host operating
system returns a file descriptor (which can be thought of as a pointer to an entry
in the open files table) that is used in all subsequent I/O operations, including all
of the read and write primitives and the close() methods in java.io and
java.net packages (assuming the destination or “sink” is a file or socket).

155. The term handle was briefly used to describe references to objects on the Java platform, but
that terminology is no longer appropriate because object references are no longer implemented
using an indirection similar to that found in file descriptor and open files tables. They are now imple-
mented as direct references (or pointers).
156. It is sometimes suggested that file descriptor limits are necessary because the file descriptor
table uses kernel memory. While this is certainly true, it is also grossly misleading. UNIX-like operat-
ing systems more or less assume a 1-to-1 correspondence between file descriptors and open files
when limiting the former. The real issue here is the total amount of memory needed for an open file,
which includes entries in the open files and v-node tables. By limiting file descriptors, the OS effec-
tively limits the total amount of memory that can be used to access files.
157. API docs for the java.io.FileDescritpor class.

830 JAVA RULES


The Java platform successfully hides all of this complexity with the exception of
passing file descriptors to a FileInputStream or FileOutputStream
constructor (which is an unusual way of sharing files discussed below) and invok-
ing fos.getFD().sync(). The latter is necessary because the sync()
operation only makes sense for files. Therefore it could not be included in the
more general stream interfaces such as DataOutputStream that might
have a destination (or sink) other than a file.
The per-process file descriptor tables are part of a larger file sharing design
that is common to all UNIX-like operating systems. For that reason it has also
become the conceptual model for Java programmers. There are three tables
involved in this design. Java programmers need only be concerned with two of
them: the per-process file descriptor tables and the open files table. The open
files table includes a current offset (also known as the offset pointer or file
position) and an access mode (also known as the open mode). In a Random-
AccessFile, the value of the current offset is returned by the getFile-
Pointer() method. The access modes are read-only for a FileInput-
Stream, write-only FileOutputStream, and read-only or read-write for a
RandomAccessFile. Unlike the canRead() and canWrite() methods
in the File class which check to make sure the file exists and that the applica-
tion has either read or write permission (as determined by the security manager,
not the local file system), access modes have nothing to do with the security
manager. For example,

import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("output.txt");
FileDescriptor fd = fos.getFD();
fos.write("testing, testing, testing".getBytes());
fd.sync();
FileInputStream fis = new FileInputStream(fd);
fis.read();
}
}

Executing this program throws the following exception.

ASSERTIONS, EXCEPTIONS, AND LOGGING 831


Exception in thread "main" java.io.IOException: Access is denied
at java.io.FileInputStream.read(Native Method)
at Test.main(Test.java:9)

The same IOException would be thrown if the file descriptor from a File-
InputStream where used to open a FileOutputStream. Given the fact
that RandomAccessFile does not have a constructor that accepts a File-
Decriptor, this means that the file descriptor from a FileInputStream
can only be passed to another FileInputStream and the file descriptor
from a FileOutputStream can only be passed to another FileOutput-
Stream . A much more common scenario than either of these is that the file
descriptor from a RandomAccessFile is passed to either a FileInput-
Stream or a FileOutputStream. The latter requires that the Random-
AccessFile was opened in the "rw", "rws", or "rwd" modes.
An entry in the open files table also includes a pointer to the third table men-
tioned above (which is sometimes referred to as the v-node table). Conceptu-
ally, the v-node table includes information about the file that is the same for all
users (unlike information in the open files table such as the access mode and
current offset that is different for each user). For example, the most important
information in a v-node table is the address(es) of the block(s) on disc. (There
may be more than one block of data if the file is fragmented.) Other information
in this table includes the values returned by the isDirectory(),
isFile(), lastModified(), and length() methods in the File
class. Having now discussed all three of the tables in this conceptual model, I
will now discuss how file sharing works.
File sharing can be defined as opening the same file more than once, which
in the Java programming language means instantiating the FileInput-
Stream , FileOutputStream, or RandomAccessFile classes. Con-
structors in those classes can be passed a string (the file name) or a File
object (a pathname). In addition, constructors in the FileInputStream and
FileOutputStream classes can be passed a FileDescriptor. If a
string or File object are passed, a separate entry is created in the open files
table. Thus, if the same file is opened in two different streams, they both have
their own access mode and file pointer (as shown in Figure 6.5). This is so com-

832 JAVA RULES


Figure 6.5 File sharing

mon that programmers may not even think of it as “file sharing.” Something very
different happens if a FileDescriptor is passed, however. The same open
files table entry is used for both streams (as shown in Figure 6.6). In C and C++
this is referred to as duplicating a file descriptor. In Java programs, file
descriptors are duplicated by invoking the getFD() method in
FileInputStream, FileOutputStream, or RandomAccessFile and
passing the File-Descriptor returned to a constructor in the
FileInputStream or FileOutputStream class (subject to the limita-
tions outlined above). Note that the FileDescriptor class is not serializ-
able. Therefore a native method is required to duplicate file descriptors
across different processes on the Java platform.
The following program shows that there is a significant difference in how
files are shared.

import java.io.*;
class Test {
static File shared = new File("output.txt");

ASSERTIONS, EXCEPTIONS, AND LOGGING 833


Figure 6.6 Duplicating a file descriptor

public static void main(String[] args) throws IOException {

/*
* Different file descriptors hence separate open files
* table entries and current offsets. The file is not
* actually closed until pw2.close() is invloked.
*/
PrintWriter pw1 = new PrintWriter(
new FileOutputStream(shared));
PrintWriter pw2 = new PrintWriter(
new FileOutputStream(shared));
pw1.print("Hello");
pw2.println(" World!");
pw1.close(); //pw1 data is overwritten
pw2.close();
printFile();
shared.delete();

/*
* Same file descriptor hence same open files table
* entry and current offset. Cannot close the file

834 JAVA RULES


* until both streams are done writing.
*/
FileOutputStream fos = new FileOutputStream(shared);
pw1 = new PrintWriter(fos);
pw2 = new PrintWriter(new FileOutputStream(fos.getFD()));
pw1.print("Hello");
pw2.println(" World!");
pw1.flush(); //cannot close or else pw2 data is lost
pw2.close();
printFile();
}

static void printFile() throws IOException {


BufferedReader in = new BufferedReader(
new FileReader(shared));
String line = in.readLine(); /* INITIAL READ */
while (line != null) {
System.out.println(line);
line = in.readLine(); /* LOOPED READ */
}
}
}

Executing this program prints

1039033138000
1039033142000
World!
Hello World!

The most significant difference is that a file for which there is more than one file
descriptor can only be closed once (because closing a file releases the file
descriptor). For example, if the pw1.flush() method invocation in bold is
changed to pw1.close(), the entry in the open files tables is deleted and the
data written to pw2 (the " World!" string) is left behind in a character encod-
ing buffer.
The lesson here is that the meaning of an “open file” is really an entry in the
open files table. If there are multiple entries for the same file, the file essentially
remains open even after a close() method invocation. However, closing a file
always writes meta data such as the last modified date to the file (as shown in

ASSERTIONS, EXCEPTIONS, AND LOGGING 835


the previous example). In effect, the file is closed from the perspective of a sin-
gle process but remains open from a system-wide perspective.
The necessity of closing files is that historically all operating systems have
limited the number of file descriptors (and by extension the number of open files)
that can be allocated by a single process. Depending on the operating system,
there are either two or three limits. Using UNIX terminology, these limits are
referred to as the soft limit, the hard limit, and the kernel limit. Both the soft
and hard limits are per-process file descriptor limits. The essential meaning of
“soft” is that a programmer can change the limit as easily as calling the
setrlimit (set resource limit) function.158 Shell commands can also be used
to change the soft and hard limits.
Soft limits are designed to prevent runaway allocations in a loop. In the 1.0
and 1.1 releases, exceeding the soft limit on the Java platform threw a
NoClassDefFoundError.159 This was changed to a FileNotFound-

Exceeding the soft limit on the Java platform throws a checked


exception with a detailed message of “Too many open files.”

Exception with a detail message of “Too many open files” as a result of Bug
Id 4027749. IOException and SocketException are also thrown with
the same detail message.
The hard limit is the maximum number of file descriptors that can be allo-
cated by a single process. If a hard limit can be changed doing so requires
superuser (or root) privileges (known as the system administrator on Windows
operating systems). Unlike soft limits, hard limits are primarily designed to
detect file descriptor leaks (the failure to close files or sockets in a long-running
application such as server).

158. This is a UNIX function. The Windows 3.x operating system had a similar setHandleCount
function. It is considered obsolete on Win32 platforms, however, and simply returns the number of
handles requested.
159. This was to be expected. For example, see the “Failure to Load Resources When All File Han-
dles Are Used” document at support.microsoft.com/default.aspx?scid=kb;en-
us;50741 in which the same problem is encountered on the Windows 3.x operating system.

836 JAVA RULES


The kernel limit is a system limit on the total number of file descriptors that
can be allocated. If the kernel limit can be changed, doing so requires recompil-
ing the kernel (which is not usually an option on proprietary operating systems).
The soft limit is always less than or equal to the hard limit which is always less
than or equal to the kernel limit. The relationship between these three resource
limits is shown as follows.

file descriptors <= soft limit <= hard limit <= kernel limit

As noted above, some operating systems only have two file descriptor limits. In
that case, one is always the kernel limit. The other may be either a soft or hard
limit (implying that either can or cannot be changed).
There is no central place where file descriptor limits (or how to change them)
are documented across all platforms and for different versions of the same
operating system. As far as I can tell not even IBM has produced such a docu-
ment. It would behoove Sun as the industry leader in cross-platform pro-
gramming to invest the time and effort into compiling such a list.
Meanwhile, from what I can see after searching the Internet for days, this is an
every man (or programmer) for himself research problem. Historically, the
soft limit has been as low as 15 or 20 handles (depending on whether or not
stdin, stdout, stdprn, stderr, and stdaux are counted). This explains

As file descriptor limits increase, the significance of a file descriptor


leak is greatly diminished except on long-running applications such
as servers.

why to this very day there is still a general perception among programmers that
closing files is critically important. The soft limit is still a relatively small power of
two such as 64, 128, 256, or 1024 on many UNIX-like operating systems. On
newer operating systems, however, the movement is towards either very
high limits or no limits at all. For example, the only limit to the number of file
handles on Win32 platforms is available memory. What about runaway alloca-
tions and detecting file descriptor (or handle) leaks? Microsoft may have gone
from one extreme to another by completely eliminating file handle limits. Not that

ASSERTIONS, EXCEPTIONS, AND LOGGING 837


it matters, however, because there is a (HotSpot VM imposed) hard limit of 2035
file descriptors (or thereabouts) for Java applications running on Win32 plat-
forms. For example,

import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
File parent = new File("", "fdleak");
parent.mkdir();
String path = parent.getAbsolutePath();
/*
* Save references so that file descriptors are
* not garbage collected
*/
FileOutputStream[] nogc = new FileOutputStream[5000];
for (int i =0; i < nogc.length; i++) {
File fname = new File(path, i + ".txt");
try {
nogc[i] = new FileOutputStream(fname);
} catch (IOException e){
System.out.println(i + " files allocated");
throw e;
}
}
}
}

Executing this program on a Win32 platform using the 1.4.1 release prints

2036 files allocated


Exception in thread "main" java.io.FileNotFoundException:
C:\fdleak\2036.txt (The system cannot find the file specified)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(FileOutputStream.java:176)
at java.io.FileOutputStream.<init>(FileOutputStream.java:131)
at Test.main(Test.java:12)

The primary Bug Id 4189011 (which as of this writing is still marked “In
progress, bug”) was submitted on November 11, 1998. It includes the following
insightful comments.
To clarify the issue, on win32 system there are three ways to open a
file:

838 JAVA RULES


1: Using Win32 API

2: Using MFC class framework lib.

3: using C-Library API (open() and fopen())

Other than the third option, i.e. option 1 and 2 have practically no limita-
tion in opening number of files. The third method is restricted (for the
reason not known to me) to open only approx. 2035 files. That is why
MS JVM is able to open unlimited (practically) files, but SUN JVM fails
after 2035 files (my guess is it is using 3rd method to open file).

The case is true only only [sic] on Win32 OS. The reason this is serious
problem ans [sic] we have to revert to MS JVM is because we werr [sic]
writing high scalable server in Java...160

Evaluation Yes, this could be a real problem for big server apps. We've
already migrated some of the win32 I/O code to use the raw win32 API
rather than the MS C runtime library; we should finish the task.
-- xxxxx@xxxxx 1998/11/11

Unfortunately even though windows can deal with more win32 handles
like you get using CreateFile, we need the C library handles for FileDe-
scriptor and those are limited to 2048.

xxxxx@xxxxx 2002-10-25

Bug Id 4779905 is a closely related bug but not so marked on the Bug Parade.
It explains the FileNotFoundException and “The system cannot find the
file specified” detail message which really should be an IOException with a
“Too many open files” detail message. I am flabbergasted that these bugs do
not get more attention. (As of this writing they collectively have only 12 votes.)
This speaks volumes as to the relative insignificance of the Windows operating
system in the unfolding story of the Java platform.
It is doubtful that UNIX-like operating systems will ever completely eliminate
file descriptor limits. They appear to be moving in the direction of very high per-
process limits that are still useful in terms of runaway allocations and detecting
file descriptor leaks. These limits are sufficiently high that only a handful of

160. Description of Bug Id 4189011.

ASSERTIONS, EXCEPTIONS, AND LOGGING 839


server applications should need to exceed them.161 In the meantime, the
HotSpot VM increases the soft limit to the hard limit using the setrlimit func-
tion on UNIX-like operating systems. It would have been reasonable to assume
this has been done since the 1.0 release because Java programmers have no
API for doing the same (nor should they), but this was apparently not done until
the 1.2 release in response to Bug Id 4138520 which is a “Too many files open”
problem involving font files.
Although most applications never come even remotely close to using all of
the file descriptors available, nothing I have said here should be construed as a
license to become careless about open files. Runaway allocations will always be
a problem, and file descriptor leaks are still a very serious problem in long-run-
ning applications such as servers. For most applications, however, the signifi-
cance of file descriptor leaks has been greatly diminished. That means closing
files as soon as possible is either good programming practice (preparing you for
the big league of writing highly scalable server applications) or at most a way of
avoiding memory leaks. For example,

new PrintWriter(new BufferedWriter(new FileWriter(fname)

This stream uses a 16K character buffer and an 8K character conversion (byte)
buffer, for a total of 24K in buffering. Failure to close such a stream wastes a lot
of memory. The point again is that open files are becoming more of a memory
leak than a file descriptor leak.
Now I am ready to discuss the other reason for explicitly closing files.
Doing so flushes internal buffers. This applies only to instances of the
FileOutput-Stream class. RandomAccessFile is unbuffered.162 Like-
wise, a File-InputStream has no buffers to flush. In these cases, either a

161. Even with these very high per-process limits the three limit design still makes sense. Imagine a
server running on the same machine as general business applications. Without the three limit
design, there is no way to detect file descriptor leaks in the server application and still have a rela-
tively low soft limit for the other applications. A runaway allocation in one of those applications that
is allowed to run up to the server hard limit could do a lot of damage. The opposing argument is that
servers that require that many file descriptors or handles are most likely going to be dedicated.
Assuming that a server application that needs 11,000 file descriptors will not be running on the
same machine as general business applications, separate soft and hard limits become a question-
able design. UNIX-like operating systems currently support both views.

840 JAVA RULES


finalizer (FileInputStream only) could close the file or the host operating
system can do so when the program exits. This is particularly true of applica-
tions that do not use very many file descriptors. It is just more efficient and bet-
ter programming style to explicitly release these resources in a finally
block. Not only does it make it perfectly clear that the resource is no longer
needed, but the resource is released sooner rather than later.
An internal buffer is either a char[] (character buffer) or byte[] (byte
buffer). Because internal buffers are implemented as arrays, they are some-
times referred to as internal buffer arrays. Table 6.2 is a summary of the
internal buffers in the java.io package. The only classes that are not buffered
are PrintWriter (if the constructor is passed a Writer such as
BufferedWriter), DataOutput-Stream and FileOutputStream.
The first two are interfaces for writing text and binary data to an output stream
that may or may not be buffered. The latter is usually wrapped by a
BufferedOutputStream for efficiency’s sake.
The close() methods in java.io are chained together so that closing
an upstream stream decorator (or filter) always closes all downstream decora-
tors as well as the source or destination. For example,

DataOutputStream out = new DataOutputStream(


new BufferedOutputStream(
new FileOutputStream("output.dat")));

out.close();

Closing the DataOutputStream also closes the BufferedOutput-


Stream and the FileOutputStream (thus releasing the file descriptor).
The API docs do not actually say that the close() methods are chained
together. Instead they say that the “stream” is closed:

162. There is an RFE for a buffered random access file. The primary Bug Id is 4056207 which at
this point (after the introduction of the java.nio package) I think it is safe to say will never be
implemented. Note that the API docs for RandomAccessFile say “A random access file
behaves like a large array of bytes stored in the file system.” They do not actually say that all writes
are immediate, however. I used to assume that until the "rws" and "rwd" access mode were
added in the 1.4 release.

ASSERTIONS, EXCEPTIONS, AND LOGGING 841


Table 6.2 Internal Output Buffers in the java.io Packagea
OutputStream or
Writer Internal Buffering

FileOutputStream NO INTERNAL BUFFERING

DataOutputStream NO INTERNAL BUFFERING

OutputStreamWriter Uses a sun.nio.cs.StreamEncoder to buffer


the encoded bytes. The characters are not buffered. Thus
the buffer is a byte buffer, not a char buffer. If the
buffer size is not passed to the constructor, uses a default
buffer size of 8192. This default byte buffer size
anticipates character data being written in ASCII,
Latin-1, or some other one-byte character encoding
scheme. Thus the 8192 byte buffer is consistent with a
character buffer of the same size. Although the encoded
bytes are buffered, each write method invocation
causes the characters passed to be immediately
converted. Because conversion operations are expensive,
it is highly recommended that an upstream character
buffer (typically a BufferedWriter) be used.

FileWriter This is a convenience class (a subclass of Output-


StreamWriter) that just makes it easy to allocate an
OutputStreamWriter using the default character
encoder as well as the default (byte) buffer size of 8192.
Everything said about OutputStreamWriter not
having a character buffer applies equally to this class. In
other words, you should wrap this class in a Buffer-
Writer to minimize the number of character
conversion operations.

BufferedOutput- If the buffer size is not passed to the constructor, uses a


Stream default byte buffer size of 512.

BufferedWriter If the buffer size is not passed to the constructor, uses a


default character buffer size of 8192.

842 JAVA RULES


Table 6.2 Internal Output Buffers in the java.io Packagea (Continued)
OutputStream or
Writer Internal Buffering

PrintStream There is a very good reason why the API docs for the
PrintStream class say: “The PrintWriter
class should be used in situations that require writing
characters rather than bytes.”b Whenever characters (a
char, char[], or String) are written to a
PrintStream which includes ALL of the print and
println methods, they are written to a Buffered-
Writer (with a default buffer size of 16K) and then
immediately flushed to an OutputStreamWriter
for character-to-byte conversion. The fact that they are
immediately flushed has nothing to do with automatic
flushing. They must be flushed every time because the
BufferedWriter and OutputStream-
Writer are added upstream. Thus PrintStream
has not one, but two upstream buffers (the second being the
character conversion byte buffer) that are flushed every time
a character or string is written to the stream or one of the
print or println methods is invoked. This makes
PrintStream (and by extension standard output and
error) very inefficient. This class should therefore be used
as little as possible.

PrintWriter There is no buffering if a Writer is passed to the


constructor. In that case, PrintWriter is merely an
interface for writing text to an output stream that may or
may not be buffered. If an OutputStream is passed
instead, the buffering is the same as a PrintStream
only more efficient because the BufferedWriter
and OutputStreamWriter used for character
conversion are added downstream. Thus they do not have
to be flushed every time a character or string is written to
the stream. Note that a downstream Buffered-
OutputWriter is a complete waste if automatic
flushing is used. The automatic flushing still serves a
purpose, however, because the character conversion byte
buffer must be flushed.

ASSERTIONS, EXCEPTIONS, AND LOGGING 843


Table 6.2 Internal Output Buffers in the java.io Packagea (Continued)
OutputStream or
Writer Internal Buffering

Writer This is a reference to the Writer returned by the


overloaded newWriter methods in the Channels
utility class in java.nio . The internal buffering is
exactly the same as it is for the OutputStream-
Writer class. Everything said about
OutputStreamWriter not having a character
buffer applies equally to this class. In other words, you
should wrap this class in a BufferWriter to
minimize the number of character conversion operations.
a. ObjectOutputStream is ommitted so as to simplify the discussion. These are J2SE and J2EE internal
buffer sizes. Internal buffer sizes in other products may vary. In particular, see Bug Id 4118847.
b. API docs for the java.io.PrintStream class.

Closes this input stream and releases any system resources associ-
ated with the stream.163 [from InputStream]

Closes this output stream and releases any system resources associ-
ated with this stream. The general contract of close is that it closes
the output stream.164 [from OutputStream]]

Close the stream.165 [from Reader]

Close the stream, flushing it first.166 [from Writer]

The implication of closing a “stream,” however, is that all of the stream decora-
tors (or filters) are closed as well as the source or destination. This is accom-
plished by chaining the close() methods together.
In output streams, flush() methods are also chained together. In this
case, one of the API docs does actually use “chain” to describe the implementa-
tion:
Flush the stream. If the stream has saved any characters from the vari-
ous write() methods in a buffer, write them immediately to their

163. API docs for the close() method in the java.io.InputStream class.
164. API docs for the close() method in the java.io.OutputStream class.
165. API docs for the close() method in the java.io.Reader class.
166. API docs for the close() method in the java.io.Writer class.

844 JAVA RULES


intended destination. Then, if that destination is another character or
byte stream, flush it. Thus one flush() invocation will flush all the
buffers in a chain of Writers and OutputStreams.167

Note that RandomAccessFile extends Object. It is not an OutputStream


and therefore cannot be wrapped by a BufferedOutputStream, nor does
RandomAccessFile have an internal buffer. Therefore it has no flush()
method.
The chaining of close() methods ends in the FileInputStream or
FileOutputStream class in which a native method closes the file. (A
native method is also required to close all files, including a Random-
AccessFile.) As of the 1.4 release, the close() methods in all three of
these classes close their FileChannel when using the java.nio package.
The chaining of flush() methods also ends in the FileOutputStream
class, but the flush() method in this class does nothing (because File-
OutputStream has no internal buffer). In other words, flush() methods in
the java.io package only flush internal buffers, not the external buffers used
by the host operating system. External buffers can only be flushed by invoking
getFD().sync() in the FileInputStream, FileOutputStream, or
RandomAccessFile classes of the java.io package or the (roughly)
equivalent force(boolean metaData) method in a FileChannel. In
the case of a RandomAccessFile, the "rws" and "rwd" access modes
added in the 1.4 release assure the external buffers are flushed after each write
(more or less replacing the sync() method).
Application programers typically do not explicitly invoke the flush() meth-
ods because the design of the java.io package is that internal buffers
are flushed whenever they are full and again when the stream is closed.
As stated above, however, failure to either explicitly flush() or close() a
buffered output stream after the last write will indeed leave unwritten data in an
internal buffer (except perhaps if automatic flushing is on, but that only applies to
the PrintStream and PrintWriter classes). For example, the following
program creates an empty file because the close() method is not invoked.

167. API docs for the flush() method in the java.io.Writer class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 845


import java.io.*;
public class Test {
public static void main(String[] args) {
try {
PrintWriter out = new PrintWriter(
new BufferedWriter(
new FileWriter("output.txt")));
out.println("testing, testing, testing");
}
catch (IOException ioe) {
System.err.println(ioe);
}
}
}

This example may surprise some programmers who expect output files to be
flushed and closed upon program exit. For example, here is part of the descrip-
tion of Bug Id 4034972, “PrintWriter does not flush buffer upon normal
program termination.”
General comments: Since no mention was made of this in the API doc's
I simply assumed that a PrintWriter would flush its buffer when the vir-
tual machine terminated (like output buffers in C do when you call
exit(int)). I would like to see the [sic] any PrintWriters I have created be
flushed when my program terminates normally.168

The problem with such behavior is that a programmer may not want the internal
buffer to be flushed. Instead, in the Java programming language internal
buffers are flushed when the close() method is invoked. It is therefore
incumbent upon application programmers to invoke the close() method after
they are done writing to a buffered output stream. (Note that the finalizer for
FileOutputStream is too far downstream to flush internal buffers.)
In the case of PrintWriter (as mentioned in the bug report), automatic
flushing is always an option, but a very expensive one. It more or less defeats
the purpose of using a buffer in the first place. Thus the default behavior for both
PrintStream and PrintWriter is that automatic flushing is turned off.

168. Description of Bug Id 4034972. There is at least one other such bug. See Bug Id 1244595,
“BufferedOutputStream does not flush even when the program exits.”

846 JAVA RULES


(Note that it is possible to create an unbuffered PrintStream which should
not need to be flushed, whereas a PrintWriter always needs to be flushed
because of the character conversion byte buffer.) So why buffer an output
stream that is automatically flushed? For example,

FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);


FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
new PrintStream(new BufferedOutputStream(fdOut, 128), true)
new PrintStream(new BufferedOutputStream(fdErr, 128), true)

This code for standard output and standard error is from the System class.
The reason why the system class buffers standard output and standard error is
in case they are written to one character at a time. In that case, buffering an out-
put stream that is automatically flushed makes sense.
It has become common practice to ignore the IOException thrown by a
close() method. In fact, this is so common that there are numerous exam-
ples in the core API. Ignoring the IOException thrown when closing an input
file is understandable because the file is no longer in use. Ignoring the IO-
Exception thrown by the close() method in an output stream, however, is
clearly not as safe because close() methods are responsible for flushing
internal buffers. In an extreme case in which a buffer never reaches capacity, the
flush() method invocation from within the body of the close() method
invokes the write(byte[] b, int off, int len) method in
FileOutputStream and is therefore responsible for actually writing the buff-
ered data to the file. Under those circumstances, putting all the write method

A single explicit flush of an output stream after the last write is advis-
able if you are going to ignore the IOException thrown by the
close() method.

invocations in a try block and then completely ignoring an IOException


thrown by the flush() method (invoked in the body of the close() method)
is like straining out gnats and swallowing camels. Contrary to the design of the
java.io package, it is therefore advisable to flush() internal buffers
before closing an output stream. For example,

ASSERTIONS, EXCEPTIONS, AND LOGGING 847


PrintWriter out = null;
try {
out = new PrintWriter(new BufferedWriter(
new FileWriter("text.out")));
…code that writes to the file omitted …
out.flush(); //because IOException in close() is ignored
} finally {
if (out != null) {
try { out.close(); } catch (Exception ignore) { }
}
}

I say this because ignoring the IOException thrown by a close() method


in the java.io package (as in this example) has become something of a pro-
grammer convention.
This problem is exacerbated by the following close() method implementa-
tion in the FilterOutputStream class, which is inherited by DataOut-
putStream as well as other FilterOutputStream subclasses.

public void close() throws IOException {


try {
flush();
} catch (IOException ignored) {
}
out.close();
}

I cannot agree with this implementation. It should be coded as follows to be con-


sistent with other close() method implementations in the java.io and
java.nio packages which do not ignore the IOException thrown by a
flush() method.

public void close() throws IOException {


flush();
out.close();
}

Obviously, ignoring the IOException thrown by the flush() method invo-


cation from within the body of close() methods is not much of a problem,
however, because it has never even been reported as a bug. If writing to a file is
going to throw an IOException it most likely will be thrown before the output

848 JAVA RULES


stream is closed. Nevertheless, it would seem prudent to explicitly invoke
flush() in a buffered output stream. This should be done in the same try
block as the write method invocations. The output stream need only be
flushed once, after the last write.
Even though the IOException thrown by close() methods is ignored,
it still must be caught unless the method or constructor in which it is invoked
throws IOException. This leads to several different styles of closing a
stream (or more generally of invoking any close() method that throws an
exception):
• Closing a stream in a method that throws IOException is the easiest
• Streams can be closed in the same try block in which they are opened and
processed using a nested try statement. I am actually surprised this
idiom is not more widely used. It is a much more elegant solution to
this problem and does not ignore the IOException thrown by
close() methods.
• The standard idiom is to close a stream in the finally block using a sep-
arate try statement. This is substantially different than the other two
approaches because the file may not have been opened. Therefore defen-
sive coding is required so that a NullPointerException is not thrown
when close() is invoked
If the method in which close() is invoked is specified to throw IOException
for some other reason, then invoking close() in the finally block is very
straightforward. The following example is of a streamlined file copy utility method.

public static void copy(File source, File destination)


throws IOException {
FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(destination);
FileChannel in = fis.getChannel();
FileChannel out = fos.getChannel();
try {
in.transferTo(0, in.size(), out);
} finally {
fis.close();
fos.close();
}
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 849


The most import detail in this example is that the files are opened before enter-
ing the try block. There is no need to close a file until it has been success-
fully opened. Constructors in the FileInputStream,
FileOutputStream , and RandomAccessFile classes do not return
null. Thus fis and fos in the previous example cannot be equal to null in
the finally block. I emphasize this point because of examples such as the
following from the core API.

InputStream in = new FileInputStream(fname);


BufferedInputStream bin = new BufferedInputStream(in);
try {
readConfiguration(bin);
} finally {
if (in != null) {
in.close();
}
}

Beside the fact that the in != null test in the finally block is completely
unnecessary, the BufferedInputStream (or bin variable) should be
closed (if for no other reason than as a matter of style), not the FileInput-
Stream (or in variable). Finding this code in the core API was something of a
surprise. It simply is not possible for in to be equal to null once the try
block is entered (unless you want to argue that a maintenance programmer may
change the code before the try block, which sounds like a stretch to me).
The following (slightly modified) example from the core API uses the second
approach of closing files in the same try block in which they are opened and
processed.

try {
File file = new File(fileName) ;
if (!file.exists())
return ;

FileInputStream in = new FileInputStream(file) ;

try { //I REALLY LIKE THIS ONE


properties.load(in) ;
} finally {

850 JAVA RULES


in.close() ;
}
} catch (Exception e) {
if (debug)
System.out.println( "properties file " + fileName +
" not found: " + e) ;
}

Here again it is safe to assume that the file was opened making it much easier to
invoke the close() method. The load(InputStream inStream)
method loads a properties file. If the file does not exist or otherwise throws an
IOException, the properties file is simply not loaded. The debug message is
a mystery, though. It incorrectly assumes that the properties files was not found.
This code is not very likely to throw a FileNotFoundException because
of the exists() method invocation.
Here are a couple examples of the third approach in which the stream is
closed in the finally block of the same try statement in which it is opened.
First an input file example:

File file = new File(filename);


BufferedReader inFile = null;
try {

inFile = new BufferedReader(new FileReader(file));

} catch (IOException e) {
} finally {
if (inFile != null) {
try {
inFile.close();
} catch (Exception e) {
}
}
}

This slightly modified example is from the core API. The IOException is
ignored because this is from a boolean method that returns false if inFile
is null. Next an output file example:

PrintWriter out = null;


try {

ASSERTIONS, EXCEPTIONS, AND LOGGING 851


out = new PrintWriter(new BufferedWriter(
new FileWriter("text.out")));
…code that writes to the file omitted …
out.flush(); //because IOException in close() is ignored
} finally {
if (out != null) {
try {
out.close();
} catch (Exception ignore) { }
}
}

This example is repeated from above as a reminder to explicitly flush() a buff-


ered output stream. These examples are what I would describe as the standard
idiom for closing streams. It is distinquished by the need to declare the outer-
most stream decorator (or filter) before entering the try block. Otherwise the
local variable that references the stream will be out of scope in the finally
block. Note that it is also necessary to initialize the local variable to null
because assignments in a try block are never definite. There is a distinct possi-
bility that the file is not actually opened in the try block because a
FileNotFoundException . The finally block therefore must be defen-
sively coded so that a NullPointerException is not thrown when
close() is invoked. Note that the close() method is only documented to
throw IOException. The catch(Exception e) exception handler in the
first of these two examples in effect goes out of its way to say “I really could care
a less about exceptions thrown by a close() method.” If you are going to do
that, why bother coding inFile != null?
The close() methods in java.io generally set both the stream to which
they are connected as well as internal buffers to null. Setting a reference type
field to null as soon as possible can also be thought of as “releasing system
resources” only in this case the system resource is heap memory. There is
some disagreement as to whether or not this is necessary for local variables.
The basis for the argument that local variables should be set to null is that
there exists the possibility that a garbage collector may mark objects as refer-
enced even though the frame from which they are referenced has already been
discarded. This is a very flimsy argument upon which to base such an

852 JAVA RULES


important coding practice. I would discourage setting local variables to null
except in extreme cases. This practice should be reserved for instance vari-
ables. Setting instance variables to null as soon as possible is especially
important for references to large objects such as buffers. Doing so makes
them immediately available for garbage collection (assuming there are no other
references to the object). For example, here are the close() methods from
Buffered-Reader and BufferedWriter in the java.io package:

public void close() throws IOException {


synchronized (lock) {
if (in == null)
return;
in.close();
in = null;
cb = null;
}
}

public void close() throws IOException {


synchronized (lock) {
if (out == null)
return;
flushBuffer();
out.close();
out = null;
buffer = null;
}
}

If you stop and think about this for a moment, setting instance variables to null
(particularly those that reference large objects) is comparable to closing a
FileInputStream in a finally clause. In both cases the alternative is to
wait for the garbage collector to run in order to release the resource.
Note the following lines of code from the close() method implementa-
tions above.

if (in == null)
return;

if (out == null)
return;

ASSERTIONS, EXCEPTIONS, AND LOGGING 853


These lines of code implement a specification that streams in the java.io
package can be closed more than once. There are a couple of problems
with the implementation of this specification, however. The first is that the behav-
ior is undocumented in binary data streams. For example,
Closes this input stream and releases any system resources associ-
ated with the stream.169

Closes this output stream and releases any system resources associ-
ated with this stream. The general contract of close is that it closes
the output stream. A closed stream cannot perform output operations
and cannot be reopened.170

These API docs are from the close() method in InputStream and
OutputStream, respectively. They say very little. The full specification for
close() methods in the java.io package can only be found in Reader
and Writer classes. For example,
Close the stream. Once a stream has been closed, further read(),
ready(), mark(), or reset() invocations will throw an
IOException. Closing a previously-closed stream, however,
has no effect. 171 [emphasis added]

Close the stream, flushing it first. Once a stream has been closed, fur-
ther write() or flush() invocations will cause an
IOException to be thrown. Closing a previously-closed stream,
however, has no effect.172 [emphasis added]

Note the generic references to “streams.” These API docs are indeed supposed
to describe the behavior of all of the close() methods in java.io. Although
all streams behave as described here, the only documented behavior for binary
data streams are comments in some (but not all) read methods that a closed
binary data input stream cannot be read. For example,

169. API docs for the close() method in java.io.InputStream class.


170. API docs for the close() method in java.io.OutputStream class.
171. API docs for the close() method in java.io.Reader class.
172. API docs for the close() method in java.io.Writer class.

854 JAVA RULES


If the first byte cannot be read for any reason other than end of file,
then an IOException is thrown. In particular, an IOException is
thrown if the input stream has been closed.173

The other problem with the specification that streams can be closed more than
once is that writes to a closed BufferOutputStream do not throw an
IOException. For example,

import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
testText();
testBinaryData();
}
static void testText() throws IOException {
PrintWriter out = new PrintWriter(
new BufferedWriter(
new FileWriter("output.txt")));
out.println("testing, testing, testing");
out.close();
out.close(); //does second close() throw IOException?
out.println("testing, testing, testing");
if (out.checkError())
System.out.println("Writing to a closed Writer " +
"throws an IOException");

BufferedReader in = new BufferedReader(


new FileReader("output.txt"));
String line = in.readLine(); /* INITIAL READ */
while (line != null) {
System.out.println(line);
line = in.readLine(); /* LOOPED READ */
}
in.close();
in.close(); //does second close() throw IOException?
try {
in.readLine();
} catch(IOException e) {
System.out.println(e.getMessage());
System.out.println("Reading from a closed Reader " +
"throws an IOException");

173. API docs for the read(byte[] b) method in java.io.DataInputStream.

ASSERTIONS, EXCEPTIONS, AND LOGGING 855


}
}
static void testBinaryData() throws IOException {
File file = new File("output.dat");
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(file)));
//DataOutputStream dos = new DataOutputStream(
// new FileOutputStream(file));
dos.writeUTF("testing, testing, testing");
dos.close();
dos.close(); //does second close() throw IOException?
int bytesWritten = 0;
try {
/* 512 is default byte buffer size */
for (int i=0; i<=512; i++) {
dos.writeUTF("this binary data is lost " +
"without throwing an exception");
bytesWritten += 8 + 54;
}
} catch(IOException e) {
System.out.println(bytesWritten + " bytes were " +
"written after closing the file");
System.out.println("Writing to a closed Buffered" +
"OutputStream does not throw " +
"an IOException until the byte " +
"buffer is full");
}

DataInputStream dis = new DataInputStream(


new BufferedInputStream(
new FileInputStream(file)));
long fileLength = file.length();
long bytesRead = 0;
while (bytesRead < fileLength) {
String s;
System.out.println(s = dis.readUTF());
bytesRead += 8 + s.length(); //UTF string
assert bytesRead <= fileLength;
}
dis.close();
dis.close(); //does second close() throw IOException?
try {
dis.readUTF();

856 JAVA RULES


} catch(IOException e) {
System.out.println(e.getMessage());
System.out.println("Reading from a closed Input" +
"Stream throws an IOException");
}
}
}

Executing this program prints

Writing to a closed Writer throws an IOException


testing, testing, testing
Stream closed
Reading from a closed Reader throws an IOException
558 bytes were written after closing the file
Writing to a closed BufferedOutputStream does not throw an
IOException until the byte buffer is full
testing, testing, testing
Stream closed
Reading from a closed InputStream throws an IOException

Binary data can be inadvertently lost without throwing an exception. If the follow-
ing change is made writing to the same closed DataOutputStream throws
an IOException immediately.

//DataOutputStream dos = new DataOutputStream(


// new BufferedOutputStream(
// new FileOutputStream(file)));
DataOutputStream dos = new DataOutputStream(
new FileOutputStream(file));

The reason for this behavior is obvious looking at the write methods in
BufferedWriter. They include a private ensureOpen() method that
throws an IOException with a detail message of "Stream closed". This
method is missing in BufferedOutputStream. Adding such a method is
not a backwards compatibility issue, and so I have reported this as a bug.
The next system resource to be discussed is the graphics context used by a
Graphics object in the java.awt package. These objects are frequently
created, used, and then disposed of. The API docs for the dispose() method
reads in part:

ASSERTIONS, EXCEPTIONS, AND LOGGING 857


Disposes of this graphics context and releases any system resources
that it is using.

Here is a typical example of invoking the dispose() method in the finally


block of a try statement.

Graphics g = getGraphics();
try {
…Graphics object used here…
} finally {
if (g != null) {
g.dispose();
}
}

In this case, the g != null test may be necessary because some get-
Graphics() methods return null.
Conventional wisdom says that finalizers should be used to free memory allo-
cated in native methods, but why should releasing this particular system
resource be any different from the others? There is an example of freeing mem-
ory allocated by a native method near the bottom of 6.5.2 Unspecified Runt-
ime Exceptions and Errors on page 751.

NOTE 6.2
The following section is heavily indebted to Doug Lea for a number of
different reasons, the most important of which is his “six general re-
sponses to…failed actions.”174 In fact this section is modelled after his
3.1.1 Exceptions in Concurrent Programming in Java. It is also
heavily influenced by an in-depth study of exception handling in the core
API. This section must be considered the proverbial “tip of the iceberg”
for two reasons. The first is that a thorough study of error recovery re-
quires a considerable knowledge of concurrent programming, which is
a subject I am not willing to broach in this volume. Thus you will not

174. Doug Lea, Concurrent Programming in Java, Second Edition, 3.1.1 Exceptions.

858 JAVA RULES


read anything about “provisional actions” or “checkpointing” in the fol-
lowing section. The other reason is that exception handling and er-
ror recovery are like bit parts in a much larger play known as
software fault tolerance, which truly is a science.

6.9 Exception Handling


Exception handling and recovery are not synonymous. Once control passes into
a catch block the exception thrown is considered “handled” no matter what.
This includes rethrowing the same exception, translating it into a different excep-
tion, or even a catch block that is empty. Thus exception handling is by defi-
nition whatever a catch block does, which may be nothing.
Error recovery (or recovery for short) is defined in terms of an object or sys-
tem being in a consistent state. Lea defines consistent state in terms of class
invariants:
The main goal in safety preservation is ensuring that all objects in a
system maintain consistent states: states in which all fields, and all
fields of other objects on which they depend, possess legal, meaningful
values. It sometimes takes hard work to nail down exactly what “legal”
and “meaningful” mean in a particular class. One path is first to estab-
lish conceptual-level invariants, for example the rules that water tank
volumes must always be between zero and their capacities. These can
usually be recast in terms of relationships among field values in the
associated concrete classes.

An object is consistent if all fields obey their invariants. Every public


method in every class should lead an object from one consistent state
to another.…

The system must also be in a consistent state, which can be seen in his discus-
sion of “antimessages”175 and “methods with externally visible effects that irre-
vocably change the real world by performing IO or actuating physical

175. Doug Lea, Concurrent Programming in Java, §3.1.1.3, “Rollback.” This is just some really
great stuff. Lea is a totally original thinker.

ASSERTIONS, EXCEPTIONS, AND LOGGING 859


devices.”175 The terms backwards recovery (or rollback) and rollforward are

The term error recovery (or recovery) is synonymous with con-


sistent state restoration, and includes both backwards and for-
wards recovery (or rollback and rollforward). As such, recovery is an
integral part of exception handling.

normally associated with databases in which log files are used to restore the
database. In object-oriented programming, backwards recovery (or rollback)
also means restoring the current object to the exact same state that it was in
when an instance method began to execute. Restoring the current object to any
other consistent state in response to failure is referred to as forwards recov-
ery (or rollforward). For a detailed discussion of consistent state restoration
or recovery (as narrowly defined in this section) see “Item 46: Strive for failure
atomicity” in Effective Java as well as 3.1.1.3 Rollback in Concurrent Pro-
gramming in Java.
This technical definition of recovery is consistent with the more common
meaning of “doing something that allows a program to continue executing”
(which is actually a combination of recovery and exception handling). That defini-
tion boils down to anything other than a catastrophic (or fatal) error. The reason
why the two uses are consistent is that a program cannot (or rather should not)
continue to execute if either the current object or system is in an inconsistent
state.
There are usually two methods involved in the throwing and catching of an
exception. Exception handing refers to the method that catches the exception.
When Lea refers to rollback and rollforward as one of the “six general
responses…failed actions”174 he is speaking from the perspective of the
method that fails. This is very confusing, however. For example, in 3.1.1.5 Retry
Lea says “you can contain local failure to the current method, rather than throw-
ing exceptions back to clients”176 and then uses the following example.

176. Doug Lea, Concurrent Programming in Java, §3.1.1.5, “Retry.”

860 JAVA RULES


class ClientUsingSocket {
Socket retryUntilConnected() throws InterruptedException
long delayTime = 5000 + (long)(Math.random() * 5000);
for (;;) {
try {
return new Socket(server, portnumber);
}
catch (IOException ex) {
Thread.sleep(delayTime);
delayTime = delayTime * 3 / 2 + 1;
}
}
}
}177

This “retry” is in response to an exception thrown in the Socket constructor. It


is not therefore locally contained. Not also that 3.1.1.2 Continuation begins: “If a
failed invocation….” Here again we are talking about an exception thrown in a
different method or constructor. This may seem like a silly thing to point out, but
the other “general responses to…failed actions”174 are clearly from the perspec-
tive of the method that fails. For example, 3.1.1.1 Abrupt termination begins as
follows.
An extreme response to failure is to let a method die immediately,
returning (usually via an exception) regardless of the state of the cur-
rent object or status of the current activity.178

Lea is making a subtle point here by characterizing the throwing of an exception


as an “extreme response.” He wants you to think about consistent state restora-
tion, which is precisely the point I am trying to make. Consistent state restora-
tion must be dealt with in the method that fails. It is not therefore an exception
handling strategy. Lea muddies the water by approaching this subject from the
perspective of the method that fails. Having now explained this, recovery is not
even mentioned in the following list of exception handling strategies. Likewise,
you should be cognizant of the difference between exception handling and
recovery. The former is whatever a catch block does. The latter is narrowly

177. Ibid. Comments were deliberately omitted.


178. Doug Lea, Concurrent Programming in Java, §3.1.1.1, “Abrupt termination.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 861


defined as consistent state restoration. The two are easily confused in methods
that catch errors such as OutOfMemoryError that are actually thrown by the
runtime system. Such methods may have to restore the current object to a con-
sistent state as well as handle the error.
The following definition of exception handling written by Brian Randell and Jie
Xu from the University of Newcastle upon Tyne shows the proper relationship
between software fault tolerance (the science), exception handling (the prac-
tical reality of a Java programmer coding a catch block), and recovery (con-
sistent state restoration).
Exception handling is often considered as being a limited form of soft-
ware fault tolerance - for example, by detecting and recovering an
error, and either ignoring the operation which generated it or by provid-
ing a pre-defined and heavily degraded response to that operation.
However, such software cannot be regarded as truly fault-tolerant since
some perceived departure from specification is likely to occur,
although the exception handling approach can result in software which
is robust in the sense that catastrophic failure can often be avoided. 179

The “pre-defined and heavily degraded response to that operation” might be the
use of default values or returning null (an exception handling strategy used
extensively in the core API during system initialization).
Characterizing what an exception handler does is not an exact science. The
following list of exception handling strategies was originally inspired by Doug
Lea’s “general responses to…failed actions”174 but has grown significantly as
the result of an in-depth study of exception handling in the core API.
• Throwing an Exception: (This must not be more generally characterized
as “abrupt completion” because executing a return statement is also
considered abrupt completion)
° Do nothing and allow the exception to propagate (which in
the case of a checked exception requires declaring that the
method or constructor throws the exception). PROPERLY

179. Brian Randell and Jie Xu, 40 Years of Computing at Newcastle, Chapter 6, “The Evolution of
the Recovery Block Concept” (Newcastle, University of Newcastle upon Tyne, 1997),
www.cs.ncl.ac.uk/old/events/anniversaries/40th/webbook/dependability/
recblocks/rec_blocks.html.

862 JAVA RULES


SPEAKING, THIS IS NOT AN EXCEPTION HANDLING
STRATEGY.
° Rethrowing the same exception: This is usually done after
releasing system resources (often referred to as “clean up”)
and/or consistent state restoration
° Exception translation and chaining: This is one the sin-
gle most important exception handling strategies in an
object-oriented programming language
° Returning false to indicate failure: Such methods are
comparable to throwing an unspecified runtime exception or
error. Both should be considered bad programming prac-
tices. See also the following discussion of returning null
° Returning null: Whoever made this an acceptable coding
practice in the core API did the Java platform a great disser-
vice. Bloch actually refers to such programmers as “bad citi-
zens” in a Bill Venner’s interview.180 Methods that are
declared to return an object should not return null. Doing
so is not one whit different from returning -1 to indicate fail-
ure. Instead of returning null such methods should either
throw an exception to indicate failure or else use the null
object pattern. See also “Item 27: Return zero-length arrays,
not nulls” in Effective Java. Another good read on this sub-
ject is “The Null Flag bug pattern” written by Eric E. Allen at
www-106.ibm.com/developerworks/library/j-
diag3.html (both dashes are part of the address)
° Exception Handling Objects: These are used for excep-
tion handling and recovery in concurrent systems. I must
therefore defer to Doug Lea. See 3.1.1.6 Handlers in Con-
current Programming in Java. I include this under abrupt
completion because passing an exception to another
method (much like returning false to indicate failure) is
roughly comparable to throwing an exception.
• Assert that the exception is not thrown
• Ignore the exception: Whenever an exception is ignored it is a good idea to
log the exception. In the case of core API methods that display diagnostic
messages (and there are a surprising number of them), 6.10.1.1 The

180. Joshua Bloch in an interview with Bill Venner entitled “A Conversation with Josh Bloch” (First
Published in JavaWorld, January 4, 2002), on the artima.com Web site (Artima Software, Inc.),
www.artima.com/intv/blochP.html.

ASSERTIONS, EXCEPTIONS, AND LOGGING 863


AWTExceptionHandler Class includes an example of how to redirect
standard output and standard error to a log file
° Squelching the exception (which is expressed in code as an
empty catch block. Be careful, however. Things are not
always as they appear. Many empty catch blocks fall
through to an entirely different exception handling strategy
such as using default values or returning null.)
° Display diagnostic information
° Setting an error flag
• Retry
• Try an alternative
° Use default values
Lists such as this are intended to show the full breadth of exception handling
strategies. Many of these exception handling strategies are discussed further in
the following subsections.

6.9.1 Rethrowing the Same Exception


There is an important distinction between catching and rethrowing the same
exception versus catching one exception and throwing another. The latter is
referred to as exception translation and is discussed in the next section.
Rethrowing the same exception is not actually an “exception handling strategy”
unless the catch block is used to rollback. This section mainly addresses a
defect in the design of the fillInStackTrace() method which is supposed
to be invoked when rethrowing the same exception.
When rethrowing an exception, if the entire catch block consists of a single
throw statement that simply rethrows the same exception, then nothing more
needs to be done. The stack trace is still valid even though the exception was
rethrown. For example,

try {
…code that may throw an OutOfMemoryError…
}
catch (OutofMemoryError e) {throw e;}
catch (Throwable e) {

…code to handle all other exceptions and errors…

864 JAVA RULES


}

If the catch block includes other code, however, simply rethrowing the same
exception may result in a stack trace that is misleading because the point-of-ori-
gin remains the same. For example,

class Test {
public static void main(String[] args) {
new Dummy().a();
}
}
class Dummy {
void a() {
b();
}
void b() {
c();
}
void c() {
try {
d();
}
catch (ArithmeticException e) {
System.out.println("peek-a-boo");
//e.fillInStackTrace()
throw e;
}
}
void d() {
e();
}
void e() {
f();
}
void f() {
int i = 0;
i /= 0;
}
}

Executing this program prints

ASSERTIONS, EXCEPTIONS, AND LOGGING 865


peek-a-boo
Exception in thread "main" java.lang.ArithmeticException: / by
zero
at Dummy.f(Test.java:30)
at Dummy.e(Test.java:26)
at Dummy.d(Test.java:23)
at Dummy.c(Test.java:15)
at Dummy.b(Test.java:11)
at Dummy.a(Test.java:8)
at Test.main(Test.java:3)

According to this stack trace, an ArithmeticException was thrown in


main. There is only one line of code in main, however, which begs the question
of what code is responsible for printing peek-a-boo. Invoking e.fill-
InStackTrace() remedies this problem, but creates another. Failure infor-
mation is lost. For example, if e.fillInStackTrace() is uncommented
the same code prints

peek-a-boo
Exception in thread "main" java.lang.ArithmeticException: / by
zero
at Dummy.c(Test.java:19)
at Dummy.b(Test.java:11)
at Dummy.a(Test.java:8)
at Test.main(Test.java:3)

There is nothing to indicate that f() was ever invoked. The best solution to this
problem as of the 1.4 release is to use exception chaining. For example,

void c() {
try {
d();
}
catch (ArithmeticException e) {
System.out.println("peek-a-boo");
throw (ArithmeticException) new ArithmeticException
(e.getMessage()).initCause(e);
}
}

ArithmeticException is a legacy exception, so initCause(Throw-


able cause) must be invoked. The stack trace now looks like this.

866 JAVA RULES


peek-a-boo
Exception in thread "main" java.lang.ArithmeticException: / by
zero
at Dummy.c(Test.java:19)
at Dummy.b(Test.java:11)
at Dummy.a(Test.java:8)
at Test.main(Test.java:3)
Caused by: java.lang.ArithmeticException: / by zero
at Dummy.f(Test.java:30)
at Dummy.e(Test.java:26)
at Dummy.d(Test.java:23)
at Dummy.c(Test.java:15)
… 3 more

This takes a little extra work but no failure information is lost. I would go as far as
to say that the introduction of exception chaining in the 1.4 release more or less
made the fillInStackTrace() method obsolete.

6.9.2 Exception Translation and Chaining


More common than either squelching or rethrowing an exception is exception
translation—catching one exception and then throwing a different one. This is
sometimes referred to as nested exceptions or wrapped exceptions, but I pre-
fer the term exception translation. There are at least four reasons why excep-
tions are translated.
• Converting checked exceptions into unchecked exceptions (usually errors)
• Converting runtime exceptions into errors (which is a mistake)
• As a matter of interface design (different layers of abstraction)
• For security reasons
All four of these are discussed in order in the remainder of this section.
At the bottom of 6.5 The Throwable Class Hierarchy, I make a strong
argument against converting runtime exceptions into errors for which I will not
apologize. Converting checked exceptions into errors, however, is what I con-
sider to be one of the most difficult subjects discussed in this chapter. It was
first touched upon in 6.5.2 Unspecified Runtime Exceptions and Errors, which
included the following use of unspecified runtime exceptions and errors in the
core API.

ASSERTIONS, EXCEPTIONS, AND LOGGING 867


Converting Checked Exceptions into Unchecked Exceptions:
There are a lot of these. The implication is that for one reason or
another the checked exception is never actually thrown. Indeed, the
comments for such catch clauses include words or phrases such as
“unexpected exception”, “internal error”, “can't happen”, “this should
never happen”, “should never occur”, “fatal”, “assertion failure”, and
the like.

I did not address this question in that section, but using assert false in such
a catch clause is dangerous. If the exception is actually thrown and assertions
are disabled, the effect is that the exception is ignored. This is not the same as
other uses of assertions because another method or constructor is involved.
Consequently, it is not as easy to establish the “in complete control” criterion for
using assertions. Only two examples of safely asserting that an exception is not
thrown come readily to mind. The first is CloneNotSupportedException
which is discussed in 6.2.3 Catching CloneNotSupportedException. The
other is the discussion of NumberFormatException that begins on page
709. For example,

try {
System.out.println(Integer.parseInt("FFFF", 16));
} catch(NumberFormatException e) {
assert false;
}

This is safe because a string literal (versus a variable) is passed. Such catch
clauses are comparable to logic traps as defined in 6.2.2 assert false and
Logic Traps (or Control-flow Invariants), only instead of a switch or nested if-
then-else statement, a catch clause is used when the checked exception in
question is provably not thrown. Beyond something as simple and straightfor-
ward as CloneNotSupportedException or passing a string literal to a
method that throws a parsing error, however, great care must be taken in
asserting that an exception is not thrown.
Now I am ready to address the rather difficult subject of throwing an unspec-
ified runtime exception or error under these circumstances. How is converting a
checked exception into an unspecified runtime exception or error any different

868 JAVA RULES


than declaring what should be checked exceptions as an extension of the
RuntimeException or Error classes? Both choices subvert the excep-
tion handling mechanism. The only answer is this question is that the excep-
tion is not thrown, which is why the responsible programmer does not want to
propagate it in the first place. If that is provably true, then assert false. Oth-
erwise, translating a checked exception into an unspecified runtime exception or
error can lead to problems as discussed in 6.5.2.1 The Chicken Little “The Sky
is Falling” Problem. If there exists any possibility that the exception can in fact be
thrown, it should most likely be translated into a high-level (checked) exception.
This is one of the main points in 6.5.2.1 The Chicken Little “The Sky is Falling”
Problem. One last word of caution, however. If you have any doubt whatsoever
as to the appropriateness of asserting that a checked exception is not thrown,
err on the side of translating it into a high-level exception. If indeed it is never
thrown, then at least no harm is done. Next I want to discuss the “must be
caught or declared to be thrown” compiler error and the desirability of a stable
throws clause. This is an import object-oriented design discussion that is
closely related to exception translation.
The JLS defines binary compatibility strictly in terms of linkage errors. Thus
changes to a throws clause do no break compatibility with existing binaries.
As stated in the JLS:
Changes to the throws clause of methods or constructors do not
break compatibility with existing binaries; these clauses are checked
only at compile time.181

Nevertheless, the checked exceptions thrown in the public methods of a


class or interface type are an important part of the interface contract. A change
in the throws clause (particularly the addition of a new checked exception) will
require that clients recompile their code at which time they will have to “catch or
declare that the exception is thrown.” The practical effect of this require-
ment is that an addition to the throws clause of a method or constructor
does in fact break compatibility with existing binaries. It is therefore desir-

181. Gosling et al., §11.2, “13.4.19 Method and Constructor Throws.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 869


able to have a throws clause that is as stable as the method or constructor
signature.
That stability is achieved through a recognition that the throws clause is a
matter of interface design, not implementation. More specifically, the names of
the checked exceptions thrown by a class or interface are just as important as
the package name, the class or interface name, the result type of a method, the
method signatures, etc.

The names of the checked exceptions thrown in a class or interface


type are a matter of interface design, not implementation.

It is important to emphasize this fact because the “must be caught or declared


to be thrown” compiler error is an invitation to change the interface design by
thoughtlessly adding the name of a checked exception thrown by the implemen-
tation of a method or constructor. To do so not only changes the interface
design but breaks encapsulation. The implementation of a class may thrown
any number of checked exceptions. Moreover, evolving implementations may
throw different checked exceptions. Exceptions thrown by the implementation of
a class or interface are implementation details.
The idea that throwing an exception can break encapsulation because it
exposes implementation details extends not only to checked exceptions but also
to runtime exceptions and errors. For example, all of the constructors in the
URL class throw MalformedURLException. That is an interface design
decision. Those constructors execute a lot of code, however, that can poten-
tially throw other checked exceptions as well as runtime exceptions. The follow-
ing try statement is from one of those constructors.

try {

} catch(MalformedURLException e) {
throw e;
} catch(Exception e) {
throw new MalformedURLException(e.getMessage());
}

870 JAVA RULES


I have carefully checked the code omitted from the try block. It actually does
not throw any checked exceptions (though an evolving implementation might). It
does throw runtime exceptions, however, in particular StringIndexOutOf-
BoundsException. Can you see how translating StringIndexOutOf-
BoundsException into a MalformedURLException is an example
encapsulating implementation details? The latter is a checked exception that is
appropriate for this layer of abstraction (the URL class). It is interesting to
note that this constructor is converting an unchecked exception into a
checked exception. This is how it should be, not the other way around.
This example demonstrates a fundamentally important point in exception
handling. In 6.5 The Throwable Class Hierarchy, I argue that runtime excep-
tions should not be explicitly caught because they are programming errors (or
bugs). Yet in 6.8.1.1 Catchall Exception Handlers I argue that catchall exception
handlers are useful. In as much as they do not explicitly catch runtime excep-
tions such as StringIndexOutOfBoundsException there is no contra-
diction. This is a question of robustness. The URL constructors predate the
introduction of exception chaining in the 1.4 release or else they would invoke
the initCause(Throwable cause) method to preserve the failure infor-
mation in the StringIndexOutOfBoundsException. In so doing they
allow for the fact that runtime exceptions (or bugs) do occur in production code.
Does the presence of a StringIndexOutOfBoundsException in a URL
constructor warrant an attempt to shut down the entire application? No! After
system initialization has completed, the decision to shut down an application
must be made by the application programmer. The proper thing for the URL pro-
grammer to do is to preserve the failure information and throw a checked excep-
tion that is appropriate for this layer of abstraction. Thus this example of
catching a StringIndexOutOfBoundsException using a catchall
exception handler and throwing a MalformedURLException instead is cen-
tral to much of what this chapter has to say about exception handling in an
object-oriented programming language. This is how it should be done. As stated
in the API docs for the Throwable class:
One reason that a throwable may have a cause is that the class that
throws it is built atop a lower layered abstraction, and an operation on

ASSERTIONS, EXCEPTIONS, AND LOGGING 871


the upper layer fails due to a failure in the lower layer. It would be bad
design to let the throwable thrown by the lower layer propagate out-
ward, as it is generally unrelated to the abstraction provided by the
upper layer. Further, doing so would tie the API of the upper layer to the
details of its implementation, assuming the lower layer's exception was
a checked exception. Throwing a “wrapped exception” (i.e., an excep-
tion containing a cause) allows the upper layer to communicate the
details of the failure to its caller without incurring either of these short-
comings. It preserves the flexibility to change the implementation of the
upper layer without changing its API (in particular, the set of exceptions
thrown by its methods). 182

This does not explicitly state that runtime exceptions and errors should also be
caught, but the “throwable thrown by the lower layer” language certainly leaves
the door open.
Exception translation based on interface design considerations is very
closely related to the discussion in 1.11.1 The Compiler-Enforced Method Con-
tract. When overriding a superclass instance method, hiding a class method, or
implementing an interface method, the choice as to which checked exception
can be throw is limited by the superclass or superinterface method declaration.
Thus the subclass programmer has no choice but to translate exceptions. This
also explains why umbrella exceptions are unique.
Exceptions may also be translated for security reasons. The following exam-
ple is from the File class.

try {

} catch (AccessControlException x) {
/* Throwing the original AccessControlException could disclose
the location of the default temporary directory, so we
re-throw a more innocuous SecurityException */
throw new SecurityException(
"Unable to create temporary file");
}

182. API docs for the java.lang.Throwable class. Although one speaks of the different “lay-
ers” of abstraction, the exceptions thrown are usually referred to as being low- and high- “level”
exceptions. I believe the author of this API doc is striving for a consistency in usage that is not gen-
erally found elsewhere.

872 JAVA RULES


The problem here is the detail message for AccessControlException
which presumably includes a pathname.
Whenever exceptions are translated, they should be chained together. The
importance of chaining exceptions together cannot be overstated. Doing
so preserves failure information that would otherwise be lost. In the terminology
of exception chaining, the low-level exception is also known as the cause. There
are two ways to chain exceptions. The first is for “legacy exceptions” that do not
have either of the constructors required to support exception chaining. By invok-
ing the initCause(Throwable cause) method inherited from the
Throwable class, exceptions from earlier releases can be chained together.
The initCause(Throwable cause) method can only be invoked once. If
the chained exception is created using one of the newer Throwable
(Throwable cause) or Throwable(String message, Throwable
cause) constructors, this method cannot be invoked at all. The rationale for
this design is that the cause of an exception is not something that should
change. In addition to exception chaining, the fact that an exception is thrown
should be written to a log file.
Exception chaining was introduced in the 1.4 release, but has been around in
a limited way ever since the 1.0 release.183 Consider the following specification
for static initialization blocks.
If a throw statement is contained in a static initializer, then a compile-
time check ensures that either its value is always an unchecked excep-
tion or its value is always caught by some try statement that contains
it. If at run-time, despite this check, the value is not caught by some
try statement that contains the throw statement, then the value is
rethrown if it is an instance of class Error or one of its subclasses;
otherwise, it is wrapped in an ExceptionInInitializerError
object, which is then thrown (§12.4.2).

The only way to implement this specification in a <clinit> (or class initializa-
tion) method is to put all of the code from static initialization blocks (and
class variable initializers) in a try block, catch Exception, and then throw

183. This is something of a misstatement because ExceptionInInitializerError was


not actually implemented until the 1.1 release.

ASSERTIONS, EXCEPTIONS, AND LOGGING 873


ExceptionInInitializerError. (Note that catching Exception also
catches any RuntimeException.) There is even a getException()
method in the ExceptionInInitializerError class that returns the
original checked exception. That method has now been superseded by the
getCause() method discussed below. Other checked exceptions such as
NamingException in the javax.naming package and its many sub-
classes implement methods comparable to getCause(). Exception-
InInitializerError and comparable implementations prior to the 1.4
release are best described as wrapped exceptions (rather than chained)
because the lower-level exception generally does not have a method such as
getException() or getCause(). Thus at most two exceptions are
involved. Other exception classes such as java.sql.SQLException, how-
ever, have had fully functional exception chaining since the 1.1 release. The fact
is that there has been a pressing need for exception chaining for some time
now, as can be seen in the number of votes for, and the sometimes heated com-
ments added to the bottom of Bug Id 4209652, “Put recursive exception han-
dling (ala InvocationTargetException) into Throwable!” I earnestly believe we are
only starting to realize the important of exception translation in object-oriented
programming languages.

6.9.3 Squelching an Exception


Why would you need an entire subsection on coding an empty catch block, or
what I prefer to think of as squelching an exception? The answer is that this is
one of those subjects where there is a great gulf between theory and practice.
Consider Bloch’s “Item 47: Don’t ignore exceptions” in which he makes the fol-
lowing statements:
Ignoring an exception is analogous to ignoring a fire alarm—and turn-
ing it off so no one else gets a chance to see if there’s a real fire. You
may get away with it, or the results may be disastrous. Whenever you
see an empty catch block, alarm bells should go off in your head. 184

184.Bloch, Effective Java, “Item 47: Don’t ignore exceptions.”

874 JAVA RULES


I would caution against installing such an alarm system because reading the
source code for the J2SE could be a deafening experience. Bloch does go on to
say that: “At the very least, the catch block should contain a comment
explaining why it is appropriate to ignore the exception.”184 This certainly
is true. I spend a lot of time reading try statements in the core API and trying to
figure out exactly why it is safe to ignore the exception thrown.
As was more or less explained in the introduction to the last chapter, techni-
cal writers live in glass houses. If and when I do feel the reckless compulsion the
thrown a stone, I make it a practice to hurl at a giant such as Bloch, Dr. Gosling,
or The Java Tutorial. My hope is that I will not awaken them. Many other soft-
ware engineers and technical writers criticize the practice of ignoring excep-
tions. For this reason alone it is worth taking a minute or two to study some
examples of ignoring exceptions in the core API.
That fact of the matter is that ignoring exceptions is a valid exception
handling strategy, one that is much more common than you may think. It must
be carefully reasoned, which is why empty catch blocks should always be com-
mented. Sometimes the reason for ignoring an exception is simply that the code
is not important enough to propagate the exception (thus causing the method or
constructor to complete abruptly). For example, the following try statement is
from the format(LogRecord record) method in the LogRecord class
of java.util.logging.

try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
record.getThrown().printStackTrace(pw);
pw.close();
sb.append(sw.toString());
} catch (Exception ex) { }

Examples such as this is when it is most appropriate to say that the exception is
being deliberately “squelched.” The primary purpose of the catch clause is to
catch the NullPointerException thrown if record.getThrown()
returns null. However, by including all of the code related to appending the
stack trace in the same try block, the responsible programmer substantially

ASSERTIONS, EXCEPTIONS, AND LOGGING 875


changes the intent. The code now effectively says that the stack trace is not
important enough to cause the formatting of a log record to complete abruptly.
This is not really a good example, however. It would have been better to follow
Bloch’s advice in “Item 39: Use exceptions only for exception conditions” and to
have coded this without the use of a try statement. For example,

Throwable thrown = record.getThrown();


if (thrown != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
thrown.printStackTrace(pw);
pw.close();
sb.append(sw.toString());
}

The intent of the programmer is now much clearer. I chose this example for a
number of reasons, one of which is that it demonstrates how discerning the
intent of a programmer who codes an empty catch block sometimes requires
a lot of thought and is therefore time consuming. That is why a comment is
always appropriate. It is not enough to say “no-op.” You really should explain
why it is safe to ignore the exception. Fearing that I may have aroused the giant,
I am going to move on now.
Many examples that appear to ignore an exception actually use default val-
ues or return null, which is an entirely different exception handling strategy.
Here is one from the 1.4 release of the System class:

try {
java.util.logging.LogManager.getLogManager().
readConfiguration();
} catch (Exception ex) {
// System.err.println("Can't read logging configuration:");
// ex.printStackTrace();
}

The readConfiguration() method reads the default logging configuration


file. While there is no code that makes it immediate obvious the exception han-
dling strategy is to use default values, that is nevertheless the design of the log-
ging API. There are very many examples such as this in the core API. Thus you

876 JAVA RULES


should be careful about interpreting the meaning of an empty catch block (or
one that merely displays diagnostic messages). If the design is to use default
values or return null that is not the same as ignoring the exception.
So far I have at most proven that squelching an exception is a questionable
exception handling strategy. Is ignoring an exception ever a valid exception han-
dling strategy? The answer is an unqualified Yes. For example, here is an outline
of the run() method used in the java.util.Timer class to execute timed
tasks:

public void run() {


try {
while (true) {
try {
…code to execute timed tasks…
} catch(InterruptedException e) {
}
}
} finally {
// Somone killed this Thread, behave as if Timer cancelled

}
}

This example of ignoring an exception cannot be faulted. The point is that you
cannot make a blanket statement such as “Don’t ignore exceptions.”185 More-
over, this exception handling strategy is very common in the core API. Doug Lea
refers to ignoring an exception as “continuation” and says:
If a failed invocation has no bearing on either the state of the caller
object or the overall functionality requirements of the current activity,
then it may be acceptable just to ignore the exception and continue for-
ward.186

It is simply the deliberate decision to keep executing even though an exception


has been thrown.

185. Joshua Bloch, Effective Java Programming Language Guide, “Item 47: Don’t ignore excep-
tions.”
186. Doug Lea, Concurrent Programming in Java, 3.1.1.2, “Continuation.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 877


6.9.4 Setting an Error Flag
What all three of the examples discussed in this section have in common is that
they are designed not to fail. The oldest and simplest of these implementations
are the PrintStream and PrintWriter classes. Every method that can
throw an IOException in these classes includes the following exception han-
dler:

catch (IOException ioe) {


trouble = true;
}

Client programmers must invoke the checkError() method to determine if


the private trouble flag has been set. This is kind of a “Damned the torpe-
does, full speed ahead” approach to exception handling.
Although dressed up a little better, the java.util.logging package
uses a similar approach to exception handling when handlers encounter a prob-
lem writing to a log. The error(String msg, Exception ex, int
code) method in ErrorManager is documented as follows.
The error method is called when a Handler failure occurs.

This method may be overridden in subclasses. The default behavior in


this base class is that the first call is reported to System.err, and sub-
sequent calls are ignored.187

The only substantial difference between this implementation and the trouble
flag in the PrintStream and PrintWriter classes is that a message is
printed to standard error the first time an error is reported. This writing to stan-
dard error is intended to inform client programmers that an error has occurred
and is therefore no substantially different than setting an error flag (except that
an error flag can be programmatically queried). In fact, I would go as far as to
say that setting a flag (and comparable implementations) is no different than
squelching and exception except that it either provides programmatic access to

187. API docs for the error(String msg, Exception ex, int code) method in
java.util.logging.ErrorManager.

878 JAVA RULES


the fact that an exception has been thrown or else displays a diagnostic mes-
sage to standard error.
As one final example of this approach to error handling, the following
ExceptionListener interface was added to the java.bean package in
the 1.4 release.

public interface ExceptionListener {


public void exceptionThrown(Exception e);
}

Here again the implementation of the exceptionThrown(Exception e)


method in the default ExceptionListioner is not very sophisticated. It
merely prints a message to standard error along with an indication that process-
ing will continue. This is why I do not consider ExceptionListener to be an
exception handling object. Exception handling objects are generally used in
much more sophisticated designs that actually recover from the exception.

6.9.5 Retry
The only examples of the exception handling strategy known as retry that I have
ever seen are try statements coded in a loop. This exception handling strategy
is sometimes referred to as resumption (versus termination, which is what the
exception mechanism normally does). Retry comes in two flavors: try forever
and try a limited number of times. The retryUntilConnected()
method in 6.9 Exception Handling (quoted from Concurrent Programming in
Java188) is an example of “try forever.” Here is an example of “try a limited num-
ber of times:”

class Test {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
long[] memoryReserve = new long[
(int)(rt.maxMemory() / 4) / 8];
System.out.println(rt.maxMemory() +
" maximum memory in bytes");
System.out.println((memoryReserve.length * 8) +

188. Doug Lea, Concurrent Programming in Java, §3.1.1.5, “Retry.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 879


" bytes in reserve (1/4)");
System.out.println(rt.freeMemory() +
" free memory in bytes");

boolean memoryLow = false;

//forces an OutOfMemoryError
rt.gc();
int dim = (int)(rt.freeMemory()/8) * 2;

for (;;) {
try {
if (memoryLow)
dim /= 2; //simulates low-memory mode
System.out.println("attempting to allocate " +
(dim*8) + " bytes");
double[] array = new double[dim];
System.out.println("allocation successful");
break;
} catch (OutOfMemoryError e) {
if (memoryLow) {
throw e;
} else {
memoryLow = true;
System.out.println("memory is low");
memoryReserve = null; //release the reserve
}
}
}
}
}

Executing this program on my machine using the default heap size and the -
verbose:gc option prints:

[GC 244K->134K(1984K), 0.0042455 secs]


[Full GC 134K->134K(1984K), 0.0095367 secs]
134217728 maximum memory in bytes
33554432 bytes in reserve (1/4)
1896608 free memory in bytes
[Full GC 32904K->32902K(34756K), 0.0098862 secs]
attempting to allocate 53447952 bytes
[GC 32902K->32902K(59000K), 0.0007967 secs]
[Full GC 32902K->32902K(59000K), 0.0100803 secs]

880 JAVA RULES


[Full GC 32902K->32902K(64704K), 0.0496566 secs]
memory is low
attempting to allocate 26723976 bytes
allocation successful

Note that the HotSpot VM will do a gc() as well as enlarge the heap before
throwing an OutOfMemoryError(). This explains why a gc() is not neces-
sary in the catch block.

6.9.6 Try an Alternative


This exception handling strategy contributes significantly to making the Java
platform robust. For example, many of the configureLogger() methods in
this chapter invoke System.exit(1) if creating a file handler throws an
IOException. The rationale for this decision is that configureLogger()
is invoked during system initialization so there is no harm in shutting down the
program. That may not always be the case, however. Here is an alternative:

private static Logger configureLogger() {


Logger logger = Logger.getLogger("com.javarules");
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
String fname = "%h/javarules.log";
try {
Handler handler = new FileHandler(fname);
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
} catch(IOException ioe) {
Handler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.CONFIG);
logger.addHandler(consoleHandler);
logger.log(Level.CONFIG,"cannot instantiate FileHandler "
+ "using " + fname + " as the pattern", ioe);
}
return logger;
}

Unable to create a file handler, this configureLogger() method uses a


console handler instead, immediately logging the fact that a file handler could

ASSERTIONS, EXCEPTIONS, AND LOGGING 881


not be created. Inasmuch as logging is not critical to most applications, this is a
sensible way to recover from the IOException.
The “try an alternative” exception handling strategy includes any use of
default values. More often than not the defaults are for property values
that could not be read from a .properties file. For example,

private static LogManager manager;


try {
className = System.getProperty("java.util.logging.manager");
if (className != null) {
Class classObject = ClassLoader.getSystemClassLoader().
loadClass(className);
manager = (LogManager) classObject.newInstance();
}
} catch (Exception ex) {
System.err.println("Could not load LogManager \"" +
className + "\"");
ex.printStackTrace();
}
if (manager == null) {
manager = new LogManager();
}

Code very similar to this is used to instantiate the LogManager class. If for
some reason the class named in the "java.util.logging.manager"
property cannot be instantiated, a vanilla log manager is used instead. Here is
another example from the Boolean class:

public static boolean getBoolean(String name) {


boolean result = false;
try {
result = toBoolean(System.getProperty(name));
}
catch (IllegalArgumentException e) { }
catch (NullPointerException e) { }
return result;
}

One of the most important details about using default values in a public
API is to document the fact. For example, the getBoolean(String
name) method includes the following documentation.

882 JAVA RULES


If there is no property with the specified name, or if the specified name
is empty or null, then false is returned.189

Sometimes methods are passed the default value that should be return. For
example,

public static Font getFont(String nm, Font font) {


String str = null;
try {
str = System.getProperty(nm);
} catch(SecurityException e) { }
if (str == null) {
return font;
}
return decode(str);
}

This method is from the java.awt.Font class. It is documented as follows.


If the specified property is not found, the font argument is returned
instead.190

There are many such methods in the core API.

6.10 Uncaught Exceptions


An uncaught exception is one for which there is no matching catch clause.
Uncaught exceptions are handled by the default exception handler, which is
the uncaughtException(Thread t, Throwable e) method in the
ThreadGroup class. As stated in the API docs, the default exception handler

When running under javaw (as is usually the case with a GUI
application) the default exception handler is useless.

is “called by the Java Virtual Machine when a thread in this thread group stops
because of an uncaught exception.”191 This crude top-level exception handler

189. API docs for the getBoolean(String name) method in the java.lang.Boolean
class.
190. API docs for the getFont(String nm, Font font) method in the java.awt.Font
class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 883


simply invokes e.printStackTrace(System.err). The following sub-
section discusses how to override the uncaughtException(Thread t,
Throwable e) method as well as other possibilities for more sophisticated
top-level exception handlers.

NOTE 6.3
In the interest of saving space I have consistently shortened “the
uncaughtException(Thread t, Throwable e) method in
the ThreadGroup class” to just “the uncaughtException
method” throughout the following section.

6.10.1 Top-level Exception Handlers


A top-level exception handler dynamically encloses all of the code in an applica-
tion program. There are three different kinds of top-level exception handlers:
• A try statement in the main method in a single-threaded application:
There is very little call for such a top-level exception handler. They are dis-
cussed briefly in this section for the sake of completeness
• A try statement in the run() method in a thread: This is the workhorse
of last ditch recovery efforts. Recovery is never attempted in the
uncaughtException method of a ThreadGroup subclass (for rea-
sons discussed below)
• The uncaughtException method in a ThreadGroup subclass:
° As the ThreadGroup for a Thread created in the main
method of any multi-threaded application (versus using
either of the following system properties, both of which are
GUI application solutions to this problem)
° The sun.awt.exception.handler system property
in a GUI application (undocumented functionality in the
EventDispatchThread class)

191. API docs for the uncaughtException(Thread t, Throwable e) method in the


java.lang.Throwable class.

884 JAVA RULES


° The awt.threadgroup system property in a GUI applica-
tion (new as of the 1.4 release)
The remainder of this section discusses each of these top-level exception han-
dlers in order.
A single-threaded application cannot override the uncaughtException
method of the main thread group, and therefore has no choice but to imple-
ment a top-level exception handler in the main method. This preempting of the
uncaught exception handler means that the main method does not throw any
checked exceptions. The application exits normally after notifying the user that
failure is immanent and logging the uncaught exception. For example,

import java.util.logging.*;
import java.io.IOException;
class Test {
private static Logger logger = configureLogger();
public static void main(String[] args) {
try {
//EXECUTE PROGRAM CODE HERE
}
catch (Throwable e) {
System.err.println();
System.err.println("A unknown error has occured and " +
"the application must be closed.");
System.err.println("Diagnostic information has been " +
"written to the log file.");
System.err.println();
//print stack trace to log file
new UncaughtException(logger, e);
}
}
private static Logger configureLogger() {
Logger logger = Logger.getLogger("com.javarules");
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
try {
Handler handler = new FileHandler("%h/javarules.log");
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
} catch(IOException e) {
e.printStackTrace();
System.exit(1);

ASSERTIONS, EXCEPTIONS, AND LOGGING 885


}
return logger;
}
}

Alternatively, the System.exit(int status) method can be invoked if a


status code (other than zero) needs to be passed outside of the application.
Note that the top-level exception handler in main does not include any code to
recover from exceptions. Generally speaking, recovery should be attempted as
close to where an exception is thrown as possible. At the point at which an
uncaught exception has completely unwound a single-threaded application, pro-
gram termination is usually a given. The UncaughtException class is dis-
cussed in one of the following subsections.
Before discussing how to code top-level exception handlers in multi-threaded
applications, there are two very important points that must be understood:
• A try statement in the main method cannot be used as a top-level excep-
tion handler in multi-threaded applications
• For reasons explained in detail below recovery is never attempted from the
uncaughtException method in a ThreadGroup subclass. The
run() method must be used for last ditch recovery efforts in a multi-
threaded application
The first point is counterintuitive and therefore difficult to understand. For example,

import java.util.*;
import java.util.logging.*;
public class Test {
private static Logger logger = configureLogger();
public static void main(String[] args) {
logger.entering("Test", "main(String[] args)", args);
try {
new DummyThread().start();
} catch(Throwable e) {
System.out.println(
"Only the start() method is dynamically enclosed. " +
"The run() method executes on a different stack.");
}
logger.exiting("Test", "main(String[] args)");
System.out.println("The main thread is dead");
}

886 JAVA RULES


private static Logger configureLogger() {
Logger logger = Logger.getLogger("unnamed");
logger.setLevel(Level.ALL);
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
logger.addHandler(handler);
return logger;
}
}
class DummyThread extends Thread {
public void run() {
new Timer().schedule(new TimerTask() {
public void run() {
throw new Error();
}
}, 5000); //five second delay
}
}

Executing this program prints

Aug 25, 2002 5:06:45 PM Test main(String[] args)


FINER: ENTRY
Aug 25, 2002 5:06:45 PM Test main(String[] args)
FINER: RETURN
The main thread is dead
java.lang.Error
at DummyThread$1.run(Test.java:31)
at java.util.TimerThread.mainLoop(Timer.java:432)
at java.util.TimerThread.run(Timer.java:382)

Between printing the main thread is dead and the stack trace there is a
five second delay. The main method in a GUI application (and most other multi-
threaded applications) is only used to start other threads and completes almost
as soon as the application is launched. (Notice the times in the ENTRY and
RETURN log records are the same.) The point is that the catch clause in
this example does not dynamically enclose the code in the run() method.
The fact that it appears to is what makes this example so counterintuitive.
Every method has to be invoked on some stack. The Thread class con-
structor (or rather the corresponding <init> method) and start() method
are executed on the same stack as main (or more generally on the same stack

ASSERTIONS, EXCEPTIONS, AND LOGGING 887


used to create the Thread object). The run() method is always the first
method executed on the new stack. Thus either the main method invoked
by the launcher or else the run() method of some other thread is always at the
bottom of the stack (as shown in Figure 6.1, “An Execution Stack” on page 687).
There is only one exception to this rule. Whenever either of these methods throw
an exception, the uncaughtException method implicitly invoked by a JVM
is executed on the same stack (and would necessarily be at the bottom of the
stack). This is the only time a method other than main or run() is at the bot-
tom of a stack.
The fact that the uncaughtException method is executed at the bot-
tom of a stack has special significance to application programmers because of
an undocumented feature of the JVM. The design of the JVM is that this method
cannot be implicitly invoked more than once. For example,

public class Test {


static Thread thread;
public static void main(String args[]) throws Throwable {
ThreadGroup group = new AWTExceptionHandler("main");
thread = new DummyThread(group, "main");
thread.start();
}
}
class DummyThread extends Thread {
public DummyThread(ThreadGroup group, String name) {
super(group, name);
}
public void run() {
System.out.println();
System.out.println("run() method invoked");
System.out.println(this.isAlive() ? "thread is alive" :
"thread is dead");
new Throwable().printStackTrace();
throw new Error();
}
}
class AWTExceptionHandler extends ThreadGroup {
public AWTExceptionHandler(String name) {
super(name);
}
public void uncaughtException(Thread t, Throwable e) {

888 JAVA RULES


System.out.println();
System.out.println(
"uncaughtException(Thread t, Throwable e)");
System.out.println("RECURSION CHECK");
/**
* Note that a thread is not technically dead if the stack
* is still in use. That includes while uncaughtException
* is executing.
*/
System.out.println(t.isAlive() ? "thread is alive" :
"thread is dead");
new Throwable().printStackTrace();
t.run();
}
}

Executing this program prints

run() method invoked


thread is alive
java.lang.Throwable
at DummyThread.run(Test.java:18)

uncaughtException(Thread t,Throwable e)
RECURSION CHECK
thread is alive
java.lang.Throwable
at AWTExceptionHandler.uncaughtException(Test.java:38)

run() method invoked


thread is alive
java.lang.Throwable
at DummyThread.run(Test.java:18)
at AWTExceptionHandler.uncaughtException(Test.java:39)

As you can see from this program the second time the run() method throws
an exception the uncaughtException method is not invoked. The original
JLS (which included the API docs for the java.lang, java.util, and
java.io packages) included the following very significant documentation for
the uncaughtException method.
The call to uncaughtException is performed by the thread that
failed to catch the exception, so t is the current thread. The call to

ASSERTIONS, EXCEPTIONS, AND LOGGING 889


uncaughtException is the last action of the thread before it dies.
If the call to uncaughtException itself results in an
(uncaught) exception, this fact is ignored and the thread merely
goes on to die.192 [emphasis added]

This paragraph was lost when the decision was made not to include API docu-
mentation in the Second Edition of the JLS. It was scant documentation in the
first place because “if the call to uncaughtException itself results in an
(uncaught) exception” is subject to interpretation. The third edition of the JLS
should clearly state that the uncaughtException method is never (implic-
itly) invoked by the JVM more than once for any given thread.
The fact that the uncaughtException method is never (implicitly)
invoked by the JVM more than once for any given thread is profoundly significant.
It means that the uncaughtException method simply cannot be used
for recovery. The reason why is simple. While it is possible to continue using the
same thread with the uncaughtException method at the bottom of the
stack, a second uncaught exception is going to kill the thread that is executing no
matter what. Thus the application programmer has lost considerable control over
the behavior of an application. This is why it is so imperative to attempt recovery in
the run() method of a thread. To some extent the declaration of the run()

Significant control over the behavior of an application is lost once the


uncaughtException method is at the bottom of the stack.

method in the Runnable interface corroborates this design choice because the
run() method has no throws clause. Thus it is more or less not possible to
attempt recovery from a checked exception in the uncaughtException
method. (I say more or less because although the run() method has no
throws clause it is still possible (though rare) for checked exceptions to reach
the uncaughtException method.

192. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, §20.21.31, “public void
uncaughtException(Thread t, Throwable e).” (Do not update.)

890 JAVA RULES


We are now ready to begin discussing how to code top-level exception han-
dlers in multi-threaded applications. The first point I want to make about the
design of top-level exception handlers is closely related to the fact that recovery
is not possible in the uncaughtException method. Top-level exception han-
dlers invoke System.exit(int status). However, this method is consid-
ered potentially harmful. As pointed out in The Java Programming Language,
the System.exit method
…abruptly terminates all threads in the runtime system, no matter what
their state. They are not interrupted, or even stopped, they simply
cease to exists as the virtual machine itself stops running—no
finally clauses are executed.193
At the point the event dispatch thread or the run() method of some other
thread has failed to handle an exception or error, however, “the program is oper-
ating ‘outside of known space,’ and attempts to continue execution are [just as]
likely to be harmful.”194 One JDC member referred to such a GUI application as
“animated corpse” and went on to say:
This, to say the least, leaves the user in a confused state, because the
application starts acting really weird and they don't know why.195

The point is that if recovery is impossible in an uncaughtException method


then invoking System.exit must not be considered harmful.
This also explains why the top-level exception handler in a run() method is
fundamentally different than the uncaughtException method in a Thread-
Group subclass. The former is the only top-level exception handler in which
recovery is possible. The latter is what I think of as a “true” top-level exception
handler. They are very easy to code because their functionality is limited to the fol-
lowing.
• Log the uncaught exception
• Inform the end-user that failure is immanent

193. Ken Arnold, James Gosling, and David Holmes, §18.3, “Shutdown.”
194. Unascribed, “Programming with Assertions” in the API docs for the 1.4 release, (Mountain
View: Sun Microsystems, 2002), “Design FAQ - The AssertionError Class.”
195. Comment from Bug Id 4063022.

ASSERTIONS, EXCEPTIONS, AND LOGGING 891


• Exit the JVM
Logging the uncaught exception, informing the user that failure is immanent, and
exiting the JVM are the primary reasons for creating a top-level exception han-
dler. This is usually described as “graceful” something:
• Graceful dying
• Graceful exiting
• Graceful termination
The operative word here is obviously graceful, the essential meaning of which is
to inform the user that a catastrophic (or fatal) error has occurred before
abruptly terminating an application. Figure 6.7 is a particularly good example
from the FrameMaker application I used to write this book.

Figure 6.7 FrameMaker example of graceful exiting

Top-level exception handlers should not delete temporary files, close sockets
and database connections, and the like. As of the 1.3 release, such code should
be placed in separate threads known as shutdown hooks that run after top-level
exception handlers. See the addShutdownHook(Thread hook) method in
Runtime as well as java.sun.com/j2se/1.3/docs/guide/lang/hook-
design.html for a discussion (the hyphen in hook-design is part of the URL).

892 JAVA RULES


In a multi-threaded application, last ditch recovery efforts must be coded in
the run() method. That is why try statements such as the following must
also be characterized as top-level exception handlers.

public void run() {


try {
//execute thread here
}
catch (ThreadDeath e) { throw e; }
catch (Throwable e) {
//make last-ditch recovery efforts here
}
}

It is interesting to note that the event dispatch thread uses a try-finally


statement (with no catch clauses) in which the finally block serves exactly
the same purpose as the catch(Throwable e) exception handler in this
example. I characterize this as last ditch recovery efforts because exceptions
should be handled as close to where they are thrown (the point-of-origin) as pos-
sible. This is a cardinal rule in exception handling.
Any exception the completely unwinds a thread could do the exact same
“damage” as ThreadDeath. Consider the following explanation from the “Why
Are Thread.stop, Thread.suspend, Thread.resume and Run-
time.runFinalizersOnExit Deprecated?” document.
Stopping a thread causes it to unlock all the monitors that it has
locked. (The monitors are unlocked as the ThreadDeath exception
propagates up the stack.) If any of the objects previously protected by
these monitors were in an inconsistent state, other threads may now
view these objects in an inconsistent state. Such objects are said to be
damaged. When threads operate on damaged objects, arbitrary
behavior can result. This behavior may be subtle and difficult to detect,
or it may be pronounced. Unlike other unchecked exceptions,
ThreadDeath kills threads silently; thus, the user has no warning
that his program may be corrupted.196

All exceptions unlock monitors and thus may result in objects that are in incon-
sistent states if allowed to propagate. Except for the fact that ThreadDeath

ASSERTIONS, EXCEPTIONS, AND LOGGING 893


“kills threads silently,” this explanation as to why the overloaded stop methods
in the Thread class were deprecated really applies to all exceptions. This
explains why it is so important to handle exceptions as close to where
they are thrown as possible. Doing so is not always possible, however.
FileNotFoundException is a perfect example. Constructors in the
java.io package are responsible for opening files. They throw
FileNotFoundException for the simple reason that only an application
programmer knows what to do if an input file cannot be found. Nevertheless,
programmers should do everything possible not to propagate exceptions. It is
the propagation of exceptions that does the “damage” described above.
Because run() methods have no throws clause, checked exceptions
must be converted to unchecked exceptions before being thrown to a real top-
level exception handler such as AWTExceptionHandler. For example, the
following code might be found at the bottom of the catch(Throwable e)
exception handler above.

if (e instanceof RuntimeException) {
throw (RuntimeException)e;
} else if (e instanceof Error) {
throw (Error)e;
} else {
throw new Error(e, "converting to an unchecked exception");
}

This highly constrained use of an unspecified error must be condoned because


exception translation at this point is meaningless.
To implement a top-level exception handler in a multi-threaded application
that can catch all of the exceptions and errors thrown not only by threads that
you create but also by threads created in the core API (more on this momen-
tarily), subclass ThreadGroup and override the uncaughtException
method. The default implementation of that method is a crude top-level excep-

196. Unascribed, “Why Are Thread.stop, Thread.suspend, Thread.resume and


Runtime.runFinalizersOnExit Deprecated?” available online at java.sun.com/j2se/
1.4.1/docs/guide/misc/threadPrimitiveDeprecation.html, (Mountain View: Sun
Microsystems, 1995-1999), “Why is Thread.stop deprecated?”

894 JAVA RULES


tion handler that prints a stack trace to standard error. Here is an example of
how to override the default exception handler in a multi-threaded application:

import javax.swing.JOptionPane;
import java.util.*;
public class Test {
public static void main(final String args[]) {
new DummyThread("main").start();
}
}
class DummyThread extends Thread {
public DummyThread(String name) {
super(new AWTExceptionHandler(), name);
}
public void run() {
//execute application here (as if this were the main method)
}
}
class AWTExceptionHandler extends ThreadGroup {
public AWTExceptionHandler() {
super("dummy");
}
public void uncaughtException(Thread t, Throwable e) {
//log uncaught exception here (omitted to shorten example)
if (e instanceof ThreadDeath) {
return;
}
String newLine = System.getProperty("line.separator");
String message =
"An unknown error has occured and" + newLine +
"the application must be closed." + newLine +
"Diagnostic information has been" + newLine +
"written to a log file. Please inform" + newLine +
"your System Administrator.";
JOptionPane.showMessageDialog(null, message,
"Fatal Error",
JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 895


Note that ThreadDeath is not caught and immediately rethrown. Most top-
level exception handlers do so, especially in multithreaded applications. I believe
this to be a misinterpretation of the JLS which states:
[A] top-level error handler that reacts to otherwise uncaught exceptions
will not print a message or otherwise signal or notify the user if the
uncaught exception is an instance of ThreadDeath.197

This does not say, however, that ThreadDeath cannot be logged. Knowing
the point of origin for a ThreadDeath object may prove to be very useful
information in a log file. The user is not informed because ThreadDeath is not
really an error. It is a poorly designed (in terms of concurrent programming)
mechanism for stopping threads. The presumption when ThreadDeath is
caught is always that the application continues to execute.
There is a great deal of misunderstanding about extending ThreadGroup
in order to override the uncaughtException method. Most of it is due to
the fact that the EventDispatchThread rather ignorantly used to intercept
all of the exceptions thrown in a GUI application and print them to standard error,
as if the application programmer had nothing to say about uncaught exceptions.
This led to the widely held misconception that classes in the core API were delib-
erately specifying what the API docs for the ThreadGroup class refer to as
the “initial thread group”198 (the system thread group, which is actually named
"system" in Sun implementations and is the thread group used by the applica-
tion launcher when creating the main thread) as the parent thread group when
creating new threads, effectively bypassing thread groups created by applica-
tion programmers. This was never actually the case. As noted in the evalua-
tion of Bug Id 4491897, “Cannot install top level Exception handler:”
There are a few instances within the JDK where we go to great lengths
to launch threads outside of the current thread group. These rare
instances typically involve system-level operations; so it is doubtful that
the user would care or want to handle uncaught exceptions from them,
but it is a hole.199

197. James Gosling, Bill Joy, and Guy Steele, §20.20.15, “public final void stop()
throws SecurityException.”
198. API docs for the java.lang.ThreadGroup class.

896 JAVA RULES


Here again, we have a counterintuitive design because although the catch
clauses in a main method do not dynamically enclose the run() methods of
other threads, the ThreadGroup for threads created in the same main
method is in fact the parent of most (if not all) other thread groups in an applica-
tion. This is very confusing.
Bug Id 4491897 (quoted in the previous paragraph) is being kept open as an
RFE, which (as I read it) is a request to override the uncaughtException
method in the system thread group (creating what would be the “ultimate” top-
level exception handler). Why has this RFE been left open and Bug Id 4063022,
“Awt ThreadGroup catches all UncaughtExceptions” been closed? The answer is
that the awt.threadgroup system property (discussed below) effectively
does for GUI applications what Bug Id 4491897 is requesting for all multi-
threaded applications. Hence Bug Id 4063022 was marked “Closed, fixed.” Has it
really been fixed though? Why should there be one solution (the awt.thread-
group system property) for GUI apps and another (less perfect) solution for
other multi-threaded applications? I maintain that there is a much larger design
issue here because programmers are genuinely confused over how best to code
a top-level exception handler in the Java programming language.
Here I have to stop and tip my hat to at least one incredibly resourceful pro-
grammer who redirected standard error (taking advantage of the fact that the
default exception handler writes to standard error) to a special implementation
of the PrintStream class in which he or she overrode the print-
ln(Object x) method as follows.

public void println(Object x) {


if (x instanceof Throwable) {
//handle uncaught exceptions here
}
}

This (somewhat desperate) solution is a clear indication that this interface design
nightmare really does need to be fixed.

199. Evaluation of Bug Id 4491897.

ASSERTIONS, EXCEPTIONS, AND LOGGING 897


The primary Bug Id 4063022, which was submitted on July 4, 1997, has
over 50 votes as of this writing (related bugs also have votes). It should not have
been marked “Closed, fixed.” The following remark added to the bottom of the
evaluation of what is clearly a related bug (though not so marked in the Bug
Parade) is interesting because it was apparently written by a senior software
engineer at Sun.
I am amenable to the idea of doing some work in this area. It makes lit-
tle sense that one has to use the ThreadGroup API to specify an excep-
tion handler.199

Given Bloch’s searing criticism of the ThreadGroup class in “Item 53: Avoid
thread groups” and what appears to be a general consensus among software
engineers at Sun that the uncaughtException method is the only practical
use of the ThreadGroup class, I should think that they would look at this as an
opportunity to deprecate the ThreadGroup class. Surely this has to be done
to prepare the way for a new major release (2.0) of the Java programming lan-
guage in which a lot of this conceptual deadweight can be tossed overboard.
This concludes the discussion of subclassing ThreadGroup for non-GUI
apps. The remainder of this section discusses the two system properties that
are used to create top-level exception handlers in GUI applications. Before doing
so, however, it is important to understand that throwing an exception or error in
a multi-threaded application does not shut down the application. If propagated to
the default exception handler, the thread will die, but the JVM is not exited. Multi-
threaded applications continue to execute until one of the following happens.
• The last non-daemon thread is exited. In a single threaded application, this is
comparable to normal completion of the main method and is described in
the API docs as exiting the JVM “normally”
• The runtime is explicitly exited, usually by invoking the System.exit
(int status) method
• The JVM is terminated externally (a.k.a. aborting) by the user pressing C
while holding down the control key (Ctrl), sometimes written as Ctrl-C
or ^C, the SIGKILL signal on UNIX or the TerminateProcess call on
Win32 (perhaps in response to a user logging off), or a system crash.

898 JAVA RULES


The following program opens a small window which you should move out of the
way so that the DOS window (or “console”) can be observed while it is executing.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ExceptionThrower {

private static String labelPrefix = "Countdown to exception: ";


private static int count = 3;
private static JFrame frame;
public static void main(String[] args) {
initializeWindow();
buildContentPane();
frame.setVisible(true); //should always be last in main
}
private static void initializeWindow() {
try { UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) { }
frame = new JFrame("Exception Thrower");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
Dimension screenSize = frame.getToolkit().getScreenSize();
frame.setSize(400, 200);
frame.setLocation(screenSize.width/2 - frame.getWidth()/2,
screenSize.height/2 - frame.getHeight()/2);
}
private static void buildContentPane() {
final JLabel countdown = new JLabel(labelPrefix + count);
JButton button = new JButton("Click here");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (--count < 0) {
count = 4;
countdown.setText("UNCAUGHT EXCEPTION THROWN");
throw new Error("Holy cow nothing happens!");
}
countdown.setText(labelPrefix + count);
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 899


});
JPanel pane = new JPanel();
pane.setBorder(BorderFactory.
createEmptyBorder(40,40,20,80));
pane.setLayout(new GridLayout(0, 1));
pane.add(new JLabel("Watch DOS in the background"));
pane.add(button);
pane.add(countdown);
frame.getContentPane().add(pane, BorderLayout.CENTER);
}
}

This is a simple GUI application (loosely modeled on the SwingApplication


in The Swing Tutorial) that should not take too long to copy. The source code
can be copied from www.javarules.com/examples.html, along with other
important (and lengthy) examples from this chapter. Executing this program
makes it very obvious that even throwing an Error has no effect what-
soever on the GUI application.
The initial response to Bug Id 4063022 is a self-described “temporary hack”
(the sun.awt.exception.handler system property) in EventDis-
patchThread that “will be removed.” This “temporary hack” is undocumented
functionality has been in place for years now. The following explanation of how to
use this system property can only be found in a private method named
handleException in the EventDispatchThread class, so the com-
ments do not show up in the API docs.
Handles an exception thrown in the event-dispatch thread.

If the system property "sun.awt.exception.handler" is


defined, then when this method is invoked it will attempt to do the fol-
lowing:

• Load the class named by the value of that property, using


the current thread's context class loader,
• Instantiate that class using its zero-argument constructor,
• Find the resulting handler object's public void handle
method, which should take a single argument of type
Throwable , and
• Invoke the handler's handle method, passing it the
thrown argument that was passed to this method.

900 JAVA RULES


If any of the first three steps fail then this method will return false
and all following invocations of this method will return false immedi-
ately. An exception thrown by the handler object's handle will be
caught, and will cause this method to return false. If the handler's
handle method is successfully invoked, then this method will return
true. This method will never throw any sort of exception.
Note: This method is a temporary hack to work around the absence of
a real API that provides the ability to replace the event-dispatch thread.
The magic "sun.awt.exception.handler" property will be
removed in a future release.200

The handleException(Throwable thrown) method is invoked in the


following if statement.

if (!handleException(e)) {
// See Bug Id 4499199.
// If we are in a modal dialog, we cannot throw
// an exception for the ThreadGroup to handle (as added
// in RFE 4063022). If we did, the message pump of
// the modal dialog would be interrupted.
// We instead choose to handle the exception ourselves.
// It may be useful to add either a runtime flag or API
// later if someone would like to instead dispose the
// dialog and allow the thread group to handle it.
if (isModal) {
System.err.println(
"Exception occurred during event dispatching:");
e.printStackTrace();
} else if (e instanceof RuntimeException) {
throw (RuntimeException)e;
} else if (e instanceof Error) {
throw (Error)e;
}
}201

200. API docs for the 1.4 release of the handleException(Throwable thrown) method
in the java.awt.EventDispatchThread class.
201. Code from the processException(Throwable e, boolean isModal) in the
java.awt.EventDispatchThread class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 901


Bug Id 4499199 was submitted after EventDispatchThread stopped
intercepting all runtime exceptions and errors. This bug is another significant
“hole” in the current design, one that will require special attention in developing
the new top-level exception handler interface. Besides all runtime exceptions and
errors thrown from modal dialogs, the event dispatch thread also intercepts
ThreadDeath.
Interestingly, the primary Bug Id 4063022 that led to the current design (use
of the awt.threadgroup system property) does not even mention the
sun.awt.exception.handler system property. Here is the complete
evaluation of that bug:
Now all exceptions are passed to ThreadGroup.uncaught-
Exception().
User can create the subclass of ThreadGroup directly from the
application, and when initialize AWT inside of it. Also programmatic
way to do it is provided. It can be done through "awt.thread-
group" system property.
I didn't provide the way for applet to install it's own ThreadGroup
since looks like none needs it and security impact of this decision is
unknown.

There was a problems with this proposal. If exception is passed to


ThreadGroup.uncaughtException() the corresponding
thread is dead. But with new AWTAutoShutdown code it is no longer
a problem. If we need EventDispatchThread again it restarts.
This makes this fix hard to backport, since it heavily relays [sic] on
AWTAutoShutdown code integrated in Merlin.202 [emphasis added]
As you can see, there is no mention of the sun.awt.exception.handler
system property whatsoever. The main problem with this “temporary hack” is
that it is not officially part of the core API and consequently there is no
guarantee that it will work in all implementations. That is why I do not
include an example that uses this system property. 203 What I have done is to

202. Evaluation of Bug Id 4063022.

902 JAVA RULES


include a no-argument constructor in the AWTExceptionHandler class so
that it can also be used as a sun.awt.exception.handler.
As noted in Bug Id 4672338, “Fix for 4063022 was not added to the API
specification” (which as of this writing is still marked “In progress, bug”), the
awt.threadgroup system property is also undocumented in the 1.4 release.
That will likely be fixed in the next feature release (1.5 or “Tiger”). Note that the
ThreadGroup subclass must be accessible from sun.awt.SunToolkit,
which means it must be declared public. Here is the code in sun.awt.Sun-
Toolkit (from the only constructor) that loads the class designated by the
awt.threadgroup system property:

/* If awt.threadgroup is set to class name the instance of


* this class is created (should be subclass of ThreadGroup)
* and EventDispatchThread is created inside of it
*
* If loaded class overrides uncaughtException instance
* handles all uncaught exception on EventDispatchThread
*/
ThreadGroup threadGroup = null;
String tgName = System.getProperty("awt.threadgroup", "");

if (tgName.length() != 0) {
try {
Constructor ctor = Class.forName(tgName).getConstructor
(new Class[] {String.class});
threadGroup = (ThreadGroup)ctor.newInstance
(new Object[] {"AWT-ThreadGroup"});
} catch (Exception e) {
System.err.println("Failed loading " + tgName +
": " + e);
}
}

This code needs to be exposed because it is attempting to load a user class


with the bootstrap class loader, which is not possible. Therefore using this sys-

203. If for some reason you are inclined to use the “temporary hack” solution to this problem, you
should be aware of the fact that the code that is responsible for loading the class designated by the
sun.awt.exception.handler system property displays no diagnostic messages if for
some reason the class cannot be loaded. This is confusing because it leads you to believe that the
class was in fact loaded.

ASSERTIONS, EXCEPTIONS, AND LOGGING 903


tem property requires the use of the non-standard -Xbootclasspath/
a:path launcher option (which appends to the end of the bootstrap classpath).
For example,

C:\Java\classes>java -Xbootclasspath/a:C:\Java\classes ExceptionThrower

Otherwise it always fails to load the class and prints a message such as the fol-
lowing to standard error.

Failed loading AWTExceptionHandler:


java.lang.ClassNotFoundException: AWTExceptionHandler

I submitted a documentation bug about this and got no response. That tells me
they are aware of the problem. Furthermore, as explained in 6.10.1.1 The
AWTExceptionHandler Class the awt.threadgroup system property
must be set before any GUI components are created. This is not true for the
sun.awt.exception.handler system property, which can be safely set
anywhere in the main method.
These are curve balls that would get by a great many programmers. Thus I
suspect the awt.threadgroup system property has gotten off to a slow
start and is not being used very much because as of this writing I can search the
entire Java Web site (including the prolific Java Discussion Forums) using the
advanced search at search.java.sun.com/search/java/advanced.jsp
and there are only two hits (the two Bug Ids mentioned at the start of this para-
graph) for “awt.threadgroup” instead of the usual hundreds or thousands.
The following modification of the ExceptionThrower class is an exam-
ple of a GUI application that uses the awt.threadgroup system property.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.logging.*;
import java.lang.reflect.Constructor;
public class ExceptionThrower {

private static Logger logger = configureLogger();


private static String labelPrefix = "Countdown to exception: ";
private static int count = 3;
private static JFrame frame;

904 JAVA RULES


public static void main(String[] args) {
/**
* Requires the use of the -Xbootclasspath/a:path
* launcher option. The next few lines of code are
* in a very specific order. The system property must
* be set before executing any GUI code. Then the
* frame is created and the AWTExceptionHandler
* initialized. In that order! The JFrame constructor
* effectively creates the AWTExceptionHandler, which
* must then be subsequently initialized.
*/
System.setProperty("awt.threadgroup",
"AWTExceptionHandler");
frame = new JFrame("Exception Thrower");
AWTExceptionHandler.getInstance().init(frame,
logger,
Redirect.BOTH);
initializeWindow();
buildContentPane();
frame.setVisible(true);
}
private static void initializeWindow() {
try { UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) { }
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
Dimension screenSize = frame.getToolkit().getScreenSize();
frame.setSize(400, 200);
frame.setLocation(screenSize.width/2 - frame.getWidth()/2,
screenSize.height/2 - frame.getHeight()/2);
}
private static void buildContentPane() {
final JLabel countdown = new JLabel(labelPrefix + count);
JButton button = new JButton("Click here");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (--count < 0) {
count = 4;
countdown.setText("UNCAUGHT EXCEPTION THROWN");
throw new Error("Holy cow nothing happens!");

ASSERTIONS, EXCEPTIONS, AND LOGGING 905


}
System.out.println("count = " + count);
countdown.setText(labelPrefix + count);
}
});
JPanel pane = new JPanel();
pane.setBorder(BorderFactory.
createEmptyBorder(40,40,20,80));
pane.setLayout(new GridLayout(0, 1));
pane.add(new JLabel("Watch DOS in the background"));
pane.add(button);
pane.add(countdown);
frame.getContentPane().add(pane, BorderLayout.CENTER);
}
private static Logger configureLogger() {
Logger logger = Logger.getLogger("com.javarules");
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
try {
Handler handler = new FileHandler
("%h/javarules.log");
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
} catch(java.io.IOException e) {
e.printStackTrace();
System.exit(1);
}
return logger;
}
}

Other than the lines of code in bold, this is the same ExceptionThrower
program from above, only now it has a much more sophisticated top-level excep-
tion handler. Note that all of the examples in this chapter set the system proper-
ties programmatically. An alternative is to use the -Dawt.threadgroup= or
-Dsun.awt.exception.handler = launcher options. Executing this pro-
gram generates a log file such as the following.

Aug 31, 2002 9:22:25 AM ExceptionThrower$2 actionPerformed


INFO: [FROM STANDARD OUTPUT] count = 2
Aug 31, 2002 9:22:25 AM ExceptionThrower$2 actionPerformed
INFO: [FROM STANDARD OUTPUT] count = 1

906 JAVA RULES


Aug 31, 2002 9:22:25 AM ExceptionThrower$2 actionPerformed
INFO: [FROM STANDARD OUTPUT] count = 0
Aug 25, 2002 12:19:08 AM ExceptionThrower$2 actionPerformed
SEVERE: UNCAUGHT EXCEPTION java.lang.Error: Holy cow nothing
happens!

Aug 25, 2002 12:19:08 AM UncaughtException logLoggableException


INFO: LOGGABLE EXCEPTION OUTPUT

Exception or Error is not Loggable

Aug 25, 2002 12:19:08 AM UncaughtException logThreadGroup


INFO: THREADS

AWTExceptionHandler[name=AWT-ThreadGroup,maxpri=10]
Thread[AWT-EventQueue-0,6,AWT-ThreadGroup]
Thread[AWT-EventQueue-0,6,AWT-ThreadGroup]

Aug 25, 2002 12:19:08 AM UncaughtException logStackTrace


INFO: STACK TRACE

Exception in thread "AWT-EventQueue-0" java.lang.Error: Holy cow nothing


happens!
at ExceptionThrower$2.actionPerformed(ExceptionThrower.java:51)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1764)

at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:144)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:136)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:99)

Aug 25, 2002 12:19:08 AM UncaughtException logPackages


INFO: PACKAGES

Using the unnamed package

Aug 25, 2002 12:19:08 AM UncaughtException logSystemProperties


INFO: SYSTEM PROPERTIES

awt.threadgroup=AWTExceptionHandler
awt.toolkit=sun.awt.windows.WToolkit
file.encoding=Cp1252
file.encoding.pkg=sun.io
file.separator=\

ASSERTIONS, EXCEPTIONS, AND LOGGING 907


java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment

user.timezone=America/New_York
user.variant=

Everything below the lines in bold is output by the UncaughtException


class. Most of the stack trace and system properties, however, has been omit-
ted to save space. The AWTExceptionHandler class includes a facility for
redirecting standard output and standard error to a log file. This is a requirement
in most GUI applications because they do not use a console window and the
java.awt and javax.swing packages include a significant number of diag-
nostic messages that are written to standard error (and a few that are written to
standard output). The first three log records are printed as the result of a
System.out.println method invocation in the actionPerform-
ed(ActionEvent e) method. This was deliberately left in the program so
that you could see what the resulting log records look like when writing to stan-
dard output or standard error. The source code for both the
AWTExceptionHandler and UncaughtException classes can be
found in the following subsections.

6.10.1.1 The AWTExceptionHandler Class


The AWTExceptionHandler is a ThreadGroup subclass that is designed
to be a general purpose top-level exception handler that can be used in any
GUI application without the need for subclassing. It can be used with either
the awt.threadgroup or sun.awt.exception.handler system
property. It is not designed to be used in non-GUI applications because the initial-
ization method init(JFrame frame, Logger logger, Redirect
redirect) must be passed a JFrame. Hence, the class name AWTExcep-
tionHandler.
The design is largely driven by the fact the class must be loaded and instan-
tiated using the core reflection API which (as always) requires predetermined
constructor signatures. That means class initialization must be deferred until
after the class is instantiated. The obvious solution to this problem is to declare
a method such as init(JFrame frame, Logger logger, Redirect

908 JAVA RULES


redirect) that can be used to pass what would normally be required con-
structor parameters. Despite the use of public constructors (also a require-
ment of the core reflection API), AWTExceptionHandler is an example of
the Singleton design pattern.
A mechanism for redirecting standard error and standard output to a log file
is built into the AWTExceptionHandler class. Doing so in a GUI application
is very important for the following reasons.
• Standard output and standard error are useless in a GUI application that
does not have a console window (one that is executed using the javaw
launcher, which includes Java Web Start)
• Even if you do not write to standard output or standard error, many of the
core API classes in the java.awt and javax.swing packages rou-
tinely catch exceptions and write critically important diagnostic messages
to standard error. Generally speaking, these same packages do not write to
standard output, but there are numerous exceptions to this rule
Thus redirecting the output from these two streams to the log file is an important
part of any top-level exception handler. Otherwise critical diagnostic information
is lost. I have implemented this functionality by redirecting standard error and
standard output (at the user’s discretion) to a ByteArrayOutput-Stream
subclass and then overriding the flush() method. This minimalist approach
makes it possible to have the redirect code in the same class as the top-level
exception handler. Diagnostic messages written to standard error are logged
using Level.WARNING. Diagnostic messages written to standard output are
logged using Level.INFO.
The awt.threadgroup system property must be set before any GUI
code is executed, even something as simple as instantiating a GUI component.
Otherwise AWTExceptionHandler (or whatever class is designated by the
awt.threadgroup system property) will not be loaded and initialized
because sun.awt.SunToolkit will have already been loaded. (Recall that it
is the constructor for sun.awt.SunToolkit that is responsible for loaded
the ThreadGroup subclass.) For example,

public class Test {


public static void main(String[] args) {

ASSERTIONS, EXCEPTIONS, AND LOGGING 909


new javax.swing.JFrame("");
}
}

Executing this program loads not only sun.awt.SunToolkit but nearly


300 other java.awt, javax.swing, sun.awt, and sun.java2d
classes. This is not a problem with the sun.awt.exception.handler
system property because the EventDispatchThread class is not loaded
until after setVisible(true) is invoked, which should mean you are out of
the main method.
Likewise, a JFrame must be created (typically the main frame of a GUI
application) before the AWTExceptionHandler can be initialized. These two
requirements dictate the order of the following lines of code from an example in
the previous section.

/**
* Requires the use of the -Xbootclasspath/a:path launcher
* option. The next few lines of code are in a very specific
* order. The system property must be set before executing any
* GUI code. Then the frame is created and the AWTExceptionHandler
* initialized. In that order! The JFrame constructor effectively
* creates the AWTExceptionHandler, which must then be
* subsequently initialized.
*/
System.setProperty("awt.threadgroup",
"AWTExceptionHandler");
frame = new JFrame("Exception Thrower");
AWTExceptionHandler.getInstance().init(frame,
logger,
Redirect.BOTH);

This may strike you as an odd design at first. I had to get used to it myself, but
given that fact that the core API is responsible for instantiating the top-level
exception handler there is not a lot of choice in the matter.
Here then is the code for the AWTExceptionHandler class:

import javax.swing.JOptionPane;
import javax.swing.JFrame;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;

910 JAVA RULES


import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.logging.Handler;
import java.util.logging.FileHandler;
import java.util.logging.MemoryHandler;
public class AWTExceptionHandler extends ThreadGroup
implements Redirect.Constants {

private static String separator = System.getProperty


("line.separator");
/**
* Essentially this class is a Singleton with public
* constructors.
*/
private static AWTExceptionHandler instance = null;
private boolean initialized = false;

private JFrame frame;


private Logger logger;
private Redirect redirect;
private String encoding;
private PrintStream sysOut;
private PrintStream sysErr;

/**
* This constructor is used when sun.awt.exception.handler
* system property is set.
*/
public AWTExceptionHandler() {
super("dummy");
if (instance != null)
throw new IllegalStateException(
"AWTExceptionHandler already exists");
instance = this;
}

/**
* This constructor is used when awt.threadgroup system
* property is set.
*/
public AWTExceptionHandler(String name) {
super(name);

ASSERTIONS, EXCEPTIONS, AND LOGGING 911


if (instance != null)
throw new IllegalStateException(
"AWTExceptionHandler already exists");
instance = this;
}

public static AWTExceptionHandler getInstance() {


if (instance == null)
throw new NullPointerException(
"AWTExceptionHandler has not been instantiated");
return instance;
}

/**
* It is recommended that both standard output and standard
* error be redirected because (1) they are useless in a GUI
* application that does not have a console window (i.e. that
* are executed using the javaw launcher, which includes
* Java Web Start) and (2) many of the core API classes
* routinely catch exceptions and write critically important
* dianostic messages to standard error.
*
* It is doubtful that synchronization is required, but is
* used as a precaution. Note also that this method can be
* invoked more than once to reinitialize the handler.
*
*/
public synchronized void init(JFrame frame,
Logger logger,
Redirect redirect) {
//a null frame is okay
if (logger == null)
throw new IllegalArgumentException("logger is null");
if (redirect == null)
throw new IllegalArgumentException("redirect is null");
instance.frame = frame;
instance.logger = logger;
instance.redirect = redirect;
initialized = true; //must be set before redirecting

/*
* The logger's encoding (preferably from a file handler)
* is used because these streams write to the log file.
*/

912 JAVA RULES


Handler[] handlers = logger.getHandlers();
for (int i=0; i<handlers.length; i++) {
if (handlers[i].getEncoding() != null) {
encoding = handlers[i].getEncoding();
if (handlers[i] instanceof FileHandler) {
break;
}
}
}

if (encoding == null) {
sysOut = new PrintStream(new StandardOutputStream(),
true);
sysErr = new PrintStream(new StandardErrorStream(),
true);
} else try {
sysOut = new PrintStream(new StandardOutputStream(),
true,
encoding);
sysErr = new PrintStream(new StandardErrorStream(),
true,
encoding);
} catch(UnsupportedEncodingException e) {assert false;}

switch(redirect.intValue()) {
case NEITHER:
break;
case STANDARD_OUTPUT:
System.setOut(sysOut);
break;
case STANDARD_ERROR:
System.setErr(sysErr);
break;
case BOTH:
System.setOut(sysOut);
System.setErr(sysErr);
default:
assert false;
}
}

public void uncaughtException(Thread t, Throwable e) {


handle(e);
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 913


public void handle(Throwable e) {
if (!initialized)
throw new IllegalStateException(
"AWTExceptionHandler has not been initialized");
new UncaughtException(logger, e).logVerbose();
/*
* The Cleaner thread (a shutdown hook in LogManager)
* closes all of the handlers, but does not push memory
* handlers (i.e. flush their buffers).
*/
Handler[] handlers = logger.getHandlers();
for (int i=0; i<handlers.length; i++) {
Handler handler = handlers[i];
if (handler instanceof MemoryHandler)
((MemoryHandler)handler).push();
}
if (e instanceof ThreadDeath) {
return;
}
String message =
"An unknown error has occured and" + separator +
"the application must be closed." + separator +
"Diagnostic information has been" + separator +
"written to a log file. Please inform" + separator +
"your System Administrator.";
JOptionPane.showMessageDialog(null, message,
"Fatal Error",
JOptionPane.ERROR_MESSAGE);
System.exit(0);
}

private boolean stackTracePrinting = false;


/**
* WARNING! Attempting to write to standard output or standard
* error in the body of this method will result in a StackOver-
* flowError. If you need to do so for testing purposes, pass
* Redirect.STANDARD_OUTPUT to the initialization method and
* then invoke System.err.println in this method(or vice versa)
*/
private void redirect(ByteArrayOutputStream stream,
Level level) {
String message = stream.toString();
if (!message.endsWith(separator))

914 JAVA RULES


return;
int index = 0; //"robust" default
StackTraceElement[] stack = new Throwable().
getStackTrace();
Search:
for (int i=0; i<stack.length; i++) {
StackTraceElement frame = stack[i];
if (frame.getClassName().equals(
"java.io.PrintStream")) {
if (frame.getMethodName().equals("println")) {
index = ++i;
break Search;
} else if (frame.getMethodName().equals
("print")) {
if (stack[i+1].getMethodName().equals
("println")) {
index = i+2;
break Search;
} else {
index = ++i;
break Search;
}
}
}
}
StackTraceElement frame = stack[index];
if (frame.getClassName().equals("java.lang.Throwable") &&
frame.getMethodName().equals("printStackTrace") &&
(!(message.indexOf(".run(") > 0))) {
if (!stackTracePrinting) {
message = "STACK TRACE" + separator +
separator + message;
stream.reset();
try {
stream.write(message.getBytes());
} catch(java.io.IOException e) { }
}
stackTracePrinting = true;
return;
}
if (stackTracePrinting) {
stackTracePrinting = false;
} else {
//log files generally don’t want the separator

ASSERTIONS, EXCEPTIONS, AND LOGGING 915


int i = separator.length();
message = message.substring(0, message.length() - i);
}

if (level.equals(Level.INFO))
message = "[FROM STANDARD OUTPUT] " + message;
else if (level.equals(Level.WARNING))
message = "[FROM STANDARD ERROR] " + message;
else
assert false;

logger.logp(level,
frame.getClassName(),
frame.getMethodName(),
message);
stream.reset();
}
class StandardOutputStream extends ByteArrayOutputStream {
public void flush() {
redirect(this, Level.INFO);
}
}
class StandardErrorStream extends ByteArrayOutputStream {
public void flush() {
redirect(this, Level.WARNING);
}
}
}

The Redirect class is a simple typesafe enum as described in 1.5.5 Enumer-


ated Types. Here is the source code:

public final class Redirect {


private final String name;
private final int value;
private Redirect(int value, String name) {
this.value = value;
this.name = name;
}
public int intValue() { return value; }
public String toString() { return name; }

//pass values (emphasize type safety)


public static final Redirect NEITHER =

916 JAVA RULES


new Redirect(Constants.NEITHER, "Neither");
public static final Redirect STANDARD_OUTPUT =
new Redirect(Constants.STANDARD_OUTPUT, "Standard output");
public static final Redirect STANDARD_ERROR =
new Redirect(Constants.STANDARD_ERROR, "Standard error");
public static final Redirect BOTH =
new Redirect(Constants.BOTH, "Both");

//comparison values (emphasize speed and convenience)


public interface Constants {
int NEITHER = 0x01;
int STANDARD_OUTPUT = 0x02;
int STANDARD_ERROR = 0x04;
int BOTH = STANDARD_OUTPUT | STANDARD_ERROR;
}
}

As stated above, it is a good idea to redirect both standard error and standard
output in a GUI application.

6.10.1.2 The UncaughtException Class


The UncaughtException class saves as much failure information as possi-
ble by writing it to a log file. At a bare minimum top-level exception handlers
should keep a copy of the stack trace. This class began life as a utility class
that had only one method that logged everything. That design bothered me until
finally I realized some users may not want to log everything. Then I changed to a
normal class in which the constructor assures that the stack trace (the bare min-
imum amount of failure information) is always saved to the log file.
Here then is the source code for the UncaughtException class:

import java.io.*;
import java.util.*;
import java.util.logging.*;
public class UncaughtException {
private Logger logger;
private Throwable uncaughtException;
private static CharArrayWriter message =
new CharArrayWriter();
private static PrintWriter log = new PrintWriter(
new BufferedWriter(message));

ASSERTIONS, EXCEPTIONS, AND LOGGING 917


private static String blankLine = System.getProperty
("line.separator");

/*
* The constructor assures that at a bare minimum the stack
* trace is always saved to the log file.
*/
public UncaughtException(Logger logger,
Throwable uncaughtException) {
if (logger == null)
throw new IllegalArgumentException("logger is null");
if (uncaughtException == null)
throw new IllegalArgumentException(
"uncaught exception is null");
this.logger = logger;
this.uncaughtException = uncaughtException;
Thread thread = Thread.currentThread();
log.println("UNCAUGHT EXCEPTION in thread \"" +
thread.getName() + "\"");
log.println();
uncaughtException.printStackTrace(log);
checkError();
StackTraceElement[] stack = uncaughtException.
getStackTrace();
StackTraceElement frame = stack[0];
logger.logp(Level.SEVERE,
frame.getClassName(),
frame.getMethodName(),
message.toString());
message.reset();
}
public void logVerbose() {
logThreadGroup();
logLoggableException();
logPackageInformation(false);
logSystemProperties();
}
public void logThreadGroup() {
log.println("THREADS");
log.println();
/*
* This is basically a workaround for the fact that the
* list() method in ThreadGroup always prints to standard
* output. There is no need to somehow reset standard

918 JAVA RULES


* output afterwards because AWTExceptionHandler only
* uses this class once, immeditely before invoking the
* System.exit method.
*/
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out, true));
Thread.currentThread().getThreadGroup().list();
log.println(out.toString());
logMessage(Level.INFO);
}
public void logLoggableException() {
log.println("LOGGABLE EXCEPTION OUTPUT");
log.println();
if (uncaughtException instanceof Loggable)
((Loggable)uncaughtException).logException(log);
else {
log.print("\t");
log.println("Exception or Error is not Loggable");
}
logMessage(Level.INFO);
}
public void logPackageInformation(boolean core) {
log.println("PACKAGES");
log.println();
/* Package class does not implement Comparable */
Package[] packages = Package.getPackages();
String[] temp = new String[packages.length];
for (int i =0; i < packages.length; i++)
temp[i] = packages[i].getName();
Arrays.sort(temp);
boolean unnamed = true;
for (int i =0; i < temp.length; i++) {
Package pkg = Package.getPackage(temp[i]);
String name = pkg.getName();
/* core API packages */
if (name.startsWith("java.") ||
name.startsWith("javax.") ||
name.startsWith("sun.") ||
name.startsWith("com.sun.") ||
name.startsWith("org.ietf.") ||
name.startsWith("org.omg.") ||
name.startsWith("org.w3c.") ||
name.startsWith("org.xml")) {
if (!core)

ASSERTIONS, EXCEPTIONS, AND LOGGING 919


continue;
}
unnamed = false;
log.println();
log.println("Name: " + name);
if (pkg.getImplementationTitle() != null) {
log.print("\t");
log.println("Implementation Title: " +
pkg.getImplementationTitle());
}
if (pkg.getImplementationVersion() != null) {
log.print("\t");
log.println("Implementation Version: " +
pkg.getImplementationVersion());
}
if (pkg.getImplementationVendor() != null) {
log.print("\t");
log.println("Implementation Vendor: " +
pkg.getImplementationVendor());
}
if (pkg.getSpecificationTitle() != null) {
log.print("\t");
log.println("Specification Title: " +
pkg.getSpecificationTitle());
}
if (pkg.getSpecificationVersion() != null) {
log.print("\t");
log.println("Specification Version: " +
pkg.getSpecificationVersion());
}
if (pkg.getSpecificationVendor() != null) {
log.print("\t");
log.println("Specification Vendor: " +
pkg.getSpecificationVendor());
}
}
if (unnamed) {
log.print("\t");
log.println("Using the unnamed package");
}
logMessage(Level.INFO);
}
public void logSystemProperties() {
log.println("SYSTEM PROPERTIES");

920 JAVA RULES


log.println();
StringBuffer buffer = new StringBuffer(2500);
/* the list() method trucates values > 40 chars */
//System.getProperties().list(log);
List sysprops = Collections.list(
System.getProperties().propertyNames());
Collections.sort(sysprops);
for (Iterator i=sysprops.listIterator(); i.hasNext();) {
String key = (String)i.next();
if (key.equals("line.separator")) {
char[] chars = System.getProperty(key).
toCharArray();
log.print(key + "=");
for (int j=0; j<chars.length; j++)
log.print(toUnicodeEscape(chars[j]));
log.println();
} else {
log.println(key + "=" + System.getProperty(key));
}
}
logMessage(Level.INFO);
}
private void logMessage(Level level) {
checkError();
StackTraceElement[] stack = new Throwable().
getStackTrace();
StackTraceElement frame = stack[1];
logger.logp(level,
frame.getClassName(),
frame.getMethodName(),
message.toString());
message.reset();
}
private void checkError() {
if (log.checkError()) { //flushes PrintWriter
logger.logp(Level.WARNING,
"UncaughtException",
"logMessage(Level level)",
"There is trouble with the PrintStream " +
"used to write to the log file");
}
}
static String toUnicodeEscape(char ch) {
String s = "" +

ASSERTIONS, EXCEPTIONS, AND LOGGING 921


Character.forDigit(((ch >>> 12) & 0xF), 16) +
Character.forDigit(((ch >>> 8) & 0xF), 16) +
Character.forDigit(((ch >>> 4) & 0xF), 16) +
Character.forDigit(((ch) & 0xF), 16);
return "\\u" + s.toUpperCase();
}
}

Note that the stack trace part of this log report includes exactly the same infor-
mation as would be printed to standard error were the default exception handler
allowed to execute. The most useful information is not shown here. It would
come from exception classes that implement the Loggable interface.

NOTE 6.4
If you have not already done so, I strongly recommend reading 6.3 An
Execution Stack Primer at the beginning of this chapter before pro-
ceeding. It is a very basic introduction to stacks.

6.10.2 A Stack Trace Primer


Stack traces are very easy to read and understand. Note that the API docs for
the Throwable class use both stack trace and backtrace, which leads to
the less common stack backtrace, and I have even seen stack traceback (in
FOLDOC). Whatever you call them, a stack trace makes the concept of a execu-
tion stack very real because you can actually “see” the activation frames
stacked on top of each other. They are primarily used to trace backwards from
the point-of-origin at which an uncaught exception was thrown through nested
method invocations to the start of a thread, which is always either the run()
method or, in the case of the main thread, the main(String[] args)
method. In other words, one of these two methods is always at the bottom of
the stack.
The API docs state that “the format of this information depends on the imple-
mentation” but that is actually more true of the JVM core dumps discussed
below. Especially since the 1.4 release, stack traces have a more or less stan-

922 JAVA RULES


dardized format (because of the StackTraceElements[] in Throwable).
For example,

class Test {
public static void main(String[] args) {
a();
}
static void a() {
b();
}
static void b() {
c();
}
static void c() {
throw new RuntimeException("detail message");
}
}

Executing this program prints

Exception in thread "main" java.lang.RuntimeException: detail


message
at Test.c(Test.java:12)
at Test.b(Test.java:9)
at Test.a(Test.java:6)
at Test.main(Test.java:3)

This is a very simple example, but nevertheless includes all of the elements in a
typical stack trace. Because the stack trace was printed by the default exception
handler (which is passed a reference to the current thread), the first line begins
with Exception in thread followed by the thread name in quotation marks.
Stack traces can also be printed by invoking one of the overloaded print-
StackTrace methods are declared in the Throwable class:

public void printStackTrace()


public void printStackTrace(java.io.PrintStream s)
public void printStackTrace(java.io.PrintWriter s)

The no-argument method is typically used in catch blocks instead of


System.err.println(e) (which of course would only print the exception
name). For example,
try {

ASSERTIONS, EXCEPTIONS, AND LOGGING 923


handler = new FileHandler("java.log");
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}

The no-argument printStackTrace() method invokes the second method


passing System.err as an argument. The third method is used to print stack
traces to a log file. Stack traces that are not printed by the default exception
handler do not include the name of the current thread. They begin with a string
presentation of the Throwable object, which is the fully qualified name of the
exception class followed by a colon and the detail message. For example,

java.lang.RuntimeException: detail message


at Test.c(Test.java:12)
at Test.b(Test.java:9)
at Test.a(Test.java:6)
at Test.main(Test.java:3)

This is the same stack trace as before only printed by invoking one of the over-
loaded printStackTrace methods. If there is no detail message the colon
is omitted.
The remaining lines in the stack trace are indented. There is one line for
each frame on the stack. Each line begins with at followed by the fully qualified
method name of the method invoked and some additional information in paren-
theses. It is the information in parentheses that causes the most confusion. Ide-
ally it is the source code file name and line number, but for a number of different
reasons that information may not be available. In the previous example, all of the
code executed came from the same Test.java file. The runtime exception
was thrown on line twelve. There are a number of different reasons why a stack
trace may not include line number:
• Unknown Source: The debugging information was not included when the
class was compiled, in which case (Unknown Source) is printed instead
• Compiled Code: The code has been somehow optimized, in which case
(Compiled Code) is printed instead
• native Method: The method is native, in which case (Native
Method) is printed instead

924 JAVA RULES


In the case of native methods, there is not much more to say. The other two,
however, require a detailed discussion.
One of the most common reasons why the source code file name and line
numbers are not available is that they simply were not included when the class
was compiled. The default is to include both (when either the -g (for debu-ger)
option is not specified or when it is specified without the colon or any keywords
following), so many programmers are not aware that this is a compiler option.
The -g option is documented as follows.
-g
Generate all debugging information, including local variables. By
default, only line number and source file information is generated.

-g:none
Do not generate any debugging information.

-g:{keyword list}
Generate only some kinds of debugging information, specified by a
comma separated list of keywords. Valid keywords are:

source
Source file debugging information

lines
Line number debugging information

vars
Local variable debugging information204

Unless the default is taken or both the source the lines keywords are
used, instead of getting line numbers what you will see is (Unknown
Source). Amusingly, novice programmers unfamiliar with stack traces some-
times mistake this to be an UnknownSource exception or error, which of
course in not really the name of an exception class. Note that if only lines is
specified (instead of both source and lines), the stack trace will still show
(Unknown Source). Note also that the compiler ignores the source,

204. Unascribed, “javac - Java programming language compiler” under “Tools and Utilities” in the
1.4.1 (and earlier) releases.

ASSERTIONS, EXCEPTIONS, AND LOGGING 925


lines , and vars keywords if they are misspelled, so you want to pay particu-
lar attention to the fact that the last two are plural.
All IDE have comparable settings. Most notably, a javac task within an
Apache Ant build file must specify debug=on. The default is off, which is
the same as using the -g:none option. For example,
<javac srcdir="${src}" destdir="${build}" debug="on" />

For more information Apache Ant, see jakarta.apache.org/ant.


There is a common misperception that the core API does not include debug-
ging information. I believe this stems from the fact that the code is often execut-
ing in mixed mode resulting in the (Compiled Code) message instead of line
numbers, but that is an entirely different subject. The core API is in fact com-
piled using the default -g compiler option so that programs can be easily
debugged. Other software packages, however, may not include debugging
information because specifying the -g:none compiler option results in a signif-
icant reduction in the size of the binaries, which of course translates into faster
downloads.
Another very common reason why the source code file name and line num-
bers are not available is that the bytecodes have been optimized. In early
releases of the javac compiler this meant using the -O compiler option, which
“directs the compiler to try to generate faster code by inlining static, final and
private methods.”205 Method inlining means there is no one-to-one correspon-
dence between the methods executing on the stack and methods in source
code. Therefore if the -O compiler option was specified no debugging informa-
tion was written to the class file. This would result in the (Unknown Source)
message.
The 1.1 release added a JIT compiler that compiled bytecodes to native
code. Compiling bytecodes to native code is entirely different than compiling
source code, but has the same problem as the -O compiler option in that meth-
ods are inlined. The JIT compiler was used by default in the Classic JVM. This is
when the (Compiled Code) message was introduced. In order to get line

205. Unascribed, “javac - The Java compiler” under “Tools Reference Pages - Windows” in the
1.1.6 (and later) releases.

926 JAVA RULES


numbers in a stack trace not only did the debugging information have to be
added at compile time, but the -nojit launcher option also had to be used.
Alternatively, -Djava.compiler=NONE could be specified on the command
line, or the JAVA_COMPILER environment variable could be set to NONE. Now
that the Classic VM is no longer supported on any platforms, the -nojit
launcher option is as antiquated as the -O compiler option.
With the HotSpot VM, there are two modes of operation. The default is
mixed-only mode (or simply mixed mode) which means that “heavily used pro-
gram segments (hot spots) are compiled to native code, and the remaining byte-
codes are executed by a bytecode interpreter.”206 As with the JIT compiler,
using the default mixed mode results in the (Compiled Code) message. In
order to see the source code file name and line numbers in stack traces the -
Xint launcher option must be used. That is a non-standard option for the inter-
preted-only mode (or interpreted mode).
One variation of (Compiled Code) includes the source code file name.
For example, (Component.java, Compiled Code). This is a good segue
into the next subject. All is not lost if you are getting (Unknown Source),
(Compiled Code), or (Native Method) in your stack traces and can do
nothing about it. If you stop and think about it for a moment, the source code file
name is highly redundant.

Unless the exception is thrown in a helper class, the source code file
name is more or less part of the fully qualified method name.

Even with inner classes it is pretty easy to determine the source code file name.
That only leaves the line number. Here it is important to understand that with the
exception of the method in which the exception or error was thrown, all of the
other methods on the stack complete abruptly at the precise point at which eval-
uating a method invocation expression or a class instance creation expression
resulted in a new activation frame being added to the stack. The name of the

206. Unascribed, “java - the Java application launcher” under “Tools and Utilities” in the 1.3 (and
later) releases.

ASSERTIONS, EXCEPTIONS, AND LOGGING 927


method or constructor invoked is in the next higher line of the stack trace. Sup-
posing that method is only invoked once or twice it should not be difficult to find
the correct line number in the source code.
If the lack of a source code file name and line number in stack traces is still
a problem, realize that these are simply code tags that make it possible for you
to find the line of code in which a Throwble object is created. An alternative is
to use error codes (or what the “Java Logging Overview” document refers to as
“unique message IDs”207) in the detail message. For example,

throw new SystemConfigurationError("EM99999: detail message");

Error codes are also discussed in 6.11.3 Logging Methods as a replacement for
the source class and method names. In either case, they solve the same problem.
In addition to being code tags, error codes are also useful when cataloguing error
messages in the documentation for very large systems.
There was a change in the 1.4 release that for the first time makes stack
traces a little difficult to read. The stack traces that print as the result of excep-
tion chaining are counterintuitive. If you think of the stack as a deck of cards, the
effect of exception chaining is to shuffle the deck. The following example is
adapted from the printStackTrace() method in the Throwable, but
uses runtime exceptions so that there is less clutter:

class Test {
public static void main(String[] args) {
try {
a();
} catch(RuntimeException e) {
e.printStackTrace();
}
}
static void a() {
try {
b();
} catch(Exception e) {
throw e;

207.Unascribed, “Java Logging Overview” in the API docs for the 1.4 release, §1.13, “Unique Mes-
sage IDs.”

928 JAVA RULES


}
}
static void b() {
c();
}
static void c() {
try {
d();
} catch(Exception e) {
throw e;
}
}
static void d() {
e();
}
static void e() {
throw new LowLevelException();
}
}
class HighLevelException extends RuntimeException {
HighLevelException(Throwable cause) {
super(cause);
}
}
class MidLevelException extends RuntimeException {
MidLevelException(Throwable cause) {
super(cause);
}
}
class LowLevelException extends RuntimeException { }

Executing this program prints

LowLevelException
at Test.e(Test.java:30)
at Test.d(Test.java:27)
at Test.c(Test.java:21)
at Test.b(Test.java:17)
at Test.a(Test.java:11)
at Test.main(Test.java:4)

ASSERTIONS, EXCEPTIONS, AND LOGGING 929


Do you see how easy this stack trace is to read? It could not be more straight
forward. Now look what happens if exception chaining is used instead of
rethrowing the same exception:

class Test {
public static void main(String[] args) {
try {
a();
} catch(RuntimeException e) {
e.printStackTrace();
}
}
static void a() {
try {
b();
} catch(RuntimeException e) {
throw new HighLevelException(e);
}
}
static void b() {
c();
}
static void c() {
try {
d();
} catch(RuntimeException e) {
throw new MidLevelException(e);
}
}
static void d() {
e();
}
static void e() {
throw new LowLevelException();
}
}

Executing this program prints

HighLevelException: MidLevelException: LowLevelException


at Test.a(Test.java:13)
at Test.main(Test.java:4)
Caused by: MidLevelException: LowLevelException

930 JAVA RULES


at Test.c(Test.java:23)
at Test.b(Test.java:17)
at Test.a(Test.java:11)
… 1 more
Caused by: LowLevelException
at Test.e(Test.java:30)
at Test.d(Test.java:27)
at Test.c(Test.java:21)
… 3 more

Now I am going to “unshuffle” the deck:

Caused by: LowLevelException


at Test.e(Test.java:30)
at Test.d(Test.java:27)
at Test.c(Test.java:21)
… 3 more
Caused by: MidLevelException: LowLevelException
at Test.c(Test.java:23)
at Test.b(Test.java:17)
at Test.a(Test.java:11)
… 1 more
HighLevelException: MidLevelException: LowLevelException
at Test.a(Test.java:13)
at Test.main(Test.java:4)

I believe this simple little trick makes it a lot easier to see that “… 3 more” is a
reference to the methods b(), a(), and main, and that “… 1 more” is a ref-
erence to main. The “deck gets shuffled” because code is being executed as
the thread is unwound. The stack trace has to reflect the methods in which code
was most recently executed. Now look back at the actual stack trace and it
should be a lot easier to read.
The overloaded printStackTrace methods in the Throwable class
(or alternatively by the dumpStack() method in the Thread class) are only
the proverbial tip of the iceberg when it comes to the diagnostic information
available without the use of -Xprof, -Xrunhprof, -Xrunxdprof,208 or

208. An “extensible, distributed profiler” available at xdprof.sourceforge.net that uses an


open source, BSD license.

ASSERTIONS, EXCEPTIONS, AND LOGGING 931


some other “profiler agent” or debugging tool. Here is a list of the “dumps”
and other diagnostic information available in a production environment:
• JVM core dump (this is the same diagnostic information output by a
JVM that has experienced “an internal error such as a segmentation vio-
lation or an illegal page fault.”209 Note that this reference to an “internal
error” is more or less a JVM crash. It has nothing to do with the
InternalError exception class.)
° Full thread dump (often referred to as a JVM thread
dump) which includes an indication of the current thread and
thread states
° Monitor cache dump (or simply a monitor dump)
° A new Deadlock Detection Utility (introduced in the 1.4.1
release of the HotSpot VM). See java.sun.com/j2se/
1.4.1/changes.html#vm for a description
• AWT component dump (using the Ctrl+Shift+F1 keys)
A JVM core dump may actually include more (or less) information than is shown
here. It depends on the operating system. Implementations that run on top of
UNIX-like operating systems print much more diagnostic information than Win-
dows’ implementations. This is doubtless a consequence of the fact that the
Java platform is a product of the UNIX community. For example, Chris White, a
lead software engineer at the IBM Centre for Java Technology in Hursley,
England, who has written perhaps the best article ever written on operating sys-
tem signals on the Java platform (from which I adopted the term JVM core
dump) says:
From a signal handling point of view, for Unix type platforms a Java
core dump contains the list of platform signals and identifies the
installed signal handler for each. (However, for z/OS only the library
containing the signal handler is referenced.) If you have an application
that uses signal handlers, then a Java core dump can be useful to
determine which function (or library) is actually being used as the han-
dler…210

209. Calvin Austin, “An Introduction to Java Stack Traces,” (Palo Alto, Sun Microsystems, 1998),
developer.java.sun.com/developer/technicalArticles/Programming/
Stacktrace.

932 JAVA RULES


For implementations that run on top of Microsoft Windows operating systems
there is no signal (or console event) information whatsoever. (Before discovering
White’s article online, I had to write a program that attempted to instantiate
every conceivable sun.misc.Signal to find out this information. It took
even more time to discover why SIGBREAK is no longer handled on Windows’
implementations.)
I coined the term AWT component dump. This is nothing more than a
string representation of all of the components in a GUI application. For example,

javax.swing.JFrame[frame0,312,284,400x200,invalid,layout=java.awt
.BorderLayout,title=ExceptionThrower,resizable,normal,defaultClos
eOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,4,41,392
x155,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=n
ull,alignmentY=null,border=,flags=1409,maximumSize=,minimumSize=,
preferredSize=],rootPaneCheckingEnabled=true]

This is the string representation of a JFrame (which includes a string represen-


tation of the root pane). If the component is invalid (i.e., needs to be laid out), the
word invalid will appear between the dimensions and the name of the layout
manager. This is very useful information when debugging an GUI app.
A JVM core dump is output to the console by pressing Break while holding
down the Ctrl key on Microsoft Windows, usually written as Ctrl+Break.
Here are a few hints:
• The Break key is the same as the Pause key and can be found some-
where on all standard keyboards.
• Make sure DOS (and not your GUI application) has the focus. This is the
opposite when pressing the Ctrl+Shift+F1 keys. The GUI application
must have focus
• On Microsoft XP keyboards, the F Lock key has to be lit. Other keyboards
might have a similar key
There are two ways to type the equivalent of Ctrl+Break for a UNIX-like oper-
ating system. The easiest is Ctrl+\. The alternative is kill -3 <pid>,

210.Chris White, “Revelations on Java signal handling and termination,” (www.ibm.com, IBM,
2002), www-106.ibm.com/developerworks/ibm/library/i-signalhandling. z/OS is
an IBM mainframe operating system.

ASSERTIONS, EXCEPTIONS, AND LOGGING 933


where <pid> is the process ID of the Java program. I have also seen this writ-
ten as kill -QUIT <pid> and kill -s SIGQUIT <pid>.
From a programmer perspective, pressing Ctrl+Break sends an operat-
ing system signal to the JVM. Signals are sometimes referred to as operating
system interrupts or external interrupts, but the preferred terminology in Sun
documentation is clearly operating system signals. Table 6.3 lists the few
operating system signals that originate from the keyboard. These are known as

Table 6.3 Operating System Signals that Originate from the Keyboard
Keyboard Operating System Signal Meaning

Pause or Ctrl+S SIGSTOP and Interrupts execution of the


SIGCONTa program until another key is
pressed.

Ctrl+Break on SIGBREAK or Prints an implementation-defined


Microsoft Windows or CTRL_BREAK_EVENT JVM core dump, which includes a
Ctrl+\ on UNIX- on Microsoft Windows b full thread dump, monitor cache
like operating
SIGQUIT on Unix dump, and the deadlock
systems
detection utility (HotSpot VM
1.4.1 and higher only)

Ctrl+C SIGINTc Kills the program (which is


particularly useful when you in an
infinite loop)
a. On the Windows platform, Pause and Ctrl+S are not mapped to a signal.
b. As per Bug Id 4416763 (confirmed in the evaluation of Bug Id 4323062) the JVM no longer installs a
SIGBREAK signal handler on the Window’s platform. A console control handler is used instead. This change
was necessary in order to implement shutdown hooks, and was in place from the 1.3 Beta through the 1.4.0
releases. As of the 1.4.1 Beta release, however, a SIGBREAK signal handler is once again being installed on
the Window’s platform. I have no idea why it was changed back.
c. The SIGINT signal handler is installed on all Sun implementations (including Microsoft Windows). Yet the
tool documentation for the -Xrs launcher option says the JVM registers a console control handler on the Win-
dows platform. I assume this means that instances of the sun.misc.Signal class are supposed to function
as cross-platform signals, but the real work of handling console events on Microsoft Windows implementations
is done in the console control handler. That might explain why SIGBREAK is being reinstalled in the 1.4.1 re-
lease, but I can find no bug report confirming this.

console events in Microsoft Windows operating systems. (They are also


referred to as “DOS Hotkeys.”) There are many other operating system signals,
however, that have nothing to do with the keyboard (such as SIGXFSZ when
the platform’s file size limit has been exceeded). The JVM handles most of the

934 JAVA RULES


signals on the Java platform. With the introduction of signal-chaining facility in the
1.4 release, however, there is greater support than ever for handling signals in a
Java program (either in native methods or using the Signal and Signal-
Handler classes in the sun.misc package).
There are three details that you should know about using Ctrl+Break (or
Ctrl+\ on UNIX-like operating systems):
• The output is sent to standard output in native code (usually expressed
as “to the console”). This is not the same standard output as defined in the
System class of a Java program. Hence, redirecting standard output
using the System.setOut(PrintStream out) method has no
effect whatsoever. The output can be redirected from the console, how-
ever. The software engineers and technical writers at Java Software seem
oblivious to this subtle difference in meaning. Even the 1.4.1 Beta docu-
mentation211 for the deadlock detection utility says that the output is
sent to “standard out” without any further explanation.
• Ctrl+Break cannot be executed programmatically without using JNI to
call the undocumented DumpThreads() and DumpMonitors() meth-
ods in the jvm.dll or alternatively calling GenerateConsoleCtrl-
Event(CTRL_BREAK_EVENT, 0) in Windows. Even if a Java program-
mers knows how to do this (and most do not), the output is still sent “to the
console” (i.e., to standard output in C code). Programmers want to be able
to invoke methods in the System or Runtime classes that return an
object that encapsulates the same runtime information (much like what was
done with the getStackTrace() method in the 1.4 release). Bug Id
4593133, “API to generate stack traces for all threads” is the primary, but
see also Bug Id 4444382 (which should be marked as related). This funca-
tionality is slated to be added in the 1.5 (“Tiger”) release.
• On a more obscure note, if the non-standard -Xrs (reduce signal) option is
used, Ctrl+Break does not work (nor are shutdown hooks run unless
System.exit(int status) is invoked).
The first two bulleted items are a particularly vexing problem because most pro-
fessional applications (including all applications launched using javaw.exe or

211. Unascribed, “Enhancements and Changes in J2SE 1.4.1 Platform” document in the 1.4.1 Beta
release.

ASSERTIONS, EXCEPTIONS, AND LOGGING 935


deployed using Java Web Start) do not use a console. Even when a console is
used, printing JVM core dumps to a console window is problematic.
For help in understanding the richness of information available in a JVM core
dump there is an article entitled “An Introduction to Java Stack Traces” available
on the JDC Web site at developer.java.sun.com/developer/technical-
Articles/Programming/Stacktrace. It is based on a talk given by Calvin Aus-
tin at the 1998 JavaOne Developer Conference. The same article was integrated
as a chapter entitled “Analyzing Stack Traces” in the book Advanced Program-
ming for the Java 2 Platform.212 The chapter is also available online at
developer.java.sun.com/developer/onlineTraining/Programming/
JDCBook/stack.html. This article is particularly useful for Java programmers
working in a UNIX environment.

NOTE 6.5
The following section discusses the logging API. I have carefully divided
this material into the following introductory section in which two exam-
ples are discussed in detail, defining basic terminology along the way,
and three advanced subsections that focus on the logger namespace,
logging configuration, logging methods, and a short section on the
Cleaner thread, which is a shutdown hook used in LogManager.
The first section reads like a tutorial and is intended for programmers
who have not yet used the logging API. Experienced programmers
should consider skipping this section and just reading the subsections
of interest.

6.11 Logging
The logging API is very easy to learn. Here is a very simple example that you can
copy and begin at once to log messages to the console:

212. Calvin Austin and Monica Pawlan, Advanced Programming for the Java 2 Platform, (Bos-
ton, Addison Wesley Professional, 2000).

936 JAVA RULES


import java.util.logging.*;
class Test {
private static Logger logger = configureLogger();
public static void main(String[] args) {
logger.entering("Test", "main(String[] args)", args);

//your code goes here

logger.exiting("Test", "main(String[] args)");


}
private static Logger configureLogger() {
Logger logger = Logger.getLogger("unnamed");
Handler handler = new ConsoleHandler();
logger.addHandler(handler);
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
return logger;
}
}

The example uses a console handler because they are by far the easiest han-
dlers to configure. Basically all you have to do is set the level. I always use a
static method named configureLogger() to both create and configure
the logger (rather than a static initialization block). This is purely a matter of
style, however.
The output from a logger is referred to as a log report. A log report is noth-
ing more than a series of log records, each of which prints on two lines (assum-
ing the use of a SimpleFormatter). If executed as is, this example prints
the following log report.

Aug 17, 2002 1:50:03 PM Test main(String[] args)


FINER: ENTRY
Aug 17, 2002 1:50:03 PM Test main(String[] args)
FINER: RETURN

The first line begins with the date and time the log record was created followed
by the name of the class and method in which the message was logging. The
second line begins with the log level (FINER in this example) followed by a
colon and then the log message, which in this example is simply ENTRY and

ASSERTIONS, EXCEPTIONS, AND LOGGING 937


RETURN indicating that the main method was entered and subsequently exited.
THROW is the another log message that is automatically generated when one of
the entering, exiting, or throwing convenience methods used for log-
ging trace information is invoked. Such messages are always capitalized. Log
messages (including automatically generated ENTRY, RETURN, and THROW
messages) are either “raw non-localized logging messages”213 or localization
keys, depending on whether or not the logger has a resource bundle.
Both the logger and handler levels in this example are set to Level.ALL
because otherwise nothing would print. If the logger or handler is set to a level
that is less than what is expected, it may appear as if the logger is not working
at all when in fact it is doing exactly what it is designed to do. The default level
for both loggers and console handlers is Level.INFO (assuming an unmodi-
fied logger configuration file), which is why Level.FINER messages (trace
information) do not print unless setLevel(Level.ALL). The point is that
not all log records are published.
Log records are subject to a battery of four different tests immediately after
they are created. First the logger compares the level of the log record against
the level of the logger. Assuming that the setFilter(Filter filter)
method has been invoked, it then invokes the isLoggable(LogRecord
record) method of the logger’s filter. If a log record fails either of these two
tests, it is said to be discarded (which means the log record is not published at
all). If the log record passes both of these logger tests then the
publish(LogRecord record) method in Logger is invoked for all of the
handlers (including parent handlers if they are used) as a means of passing the
log record to handlers. That method then immediately subjects the record to the
same two tests, only this time using the handler’s level and filter (if any). Thus the
logger determines if a particular log record should be discarded and individual
handlers then determine if it should be published.

213. API docs for the LogRecord(Level level, String msg) constructor in the
java.util.logging.LogRecord class.

938 JAVA RULES


There are seven standard log levels declared in the Level class (a typesafe
enum). Table 6.4 includes a description of each. The “Java Logging Overview”
document describes them as follows.
Each log message has an associated log Level. The Level gives a
rough guide to the importance and urgency of a log message. Log
level objects encapsulate an integer value, with higher values indicating
higher priorities.214

You may wonder why Level.OFF is included at the top of this table and
Level.ALL at the bottom (which superficially seems reversed). These are
not log levels. They are special values used only by loggers and handlers.
Using this table, if the log level is below the level of the logger, the log record is
discarded. This implies that all log records are discarded if the logger is set to
Level.OFF. Again using this table, if the log level is below the level of a han-
dler the log record is not published by that particular handler. Hence
Level.OFF is at the top and Level.ALL is at the bottom.

Table 6.4 The Seven Standard Log Levels Plus OFF and ALL
Level Description

Level.OFF Using this level effectively turns off logging.

Level.SEVERE This level indicates a “serious failure.”a The most obvious


example of this is logging an uncaught exception before
abruptly terminating an application.

Level.WARNING This level indicates a “potential problem.”a

Level.INFO This level is used for “information messages.”a

Level.CONFIG This level is used for “static configuration messages.”a


These are irregularities that occur when reading user
preferences, configuration files, and the like.

214. Unascribed, “Java Logging Overview” in the API docs for the 1.4 release, §1.2, “Log Levels.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 939


Table 6.4 The Seven Standard Log Levels Plus OFF and ALL
Level Description
a
Level.FINE These levels are used to log “tracing information.” For
Level.FINER example, Level.FINER is used by the entering
Level.FINEST and exit methods. That is the measuring stick for
logging other trace information. If Level.FINER is
used for entering and exiting a method, it’s hard to
imagine what Level.FINEST would be used for. Plus
you really have to stop and think about these method
invocations. Even with logging disabled, they are not
without cost.

Level.ALL All messages are logged.

a. API docs for fields in the java.util.logging.Level class.

Some programmers think that SEVERE, WARNING, and INFO should be


used exclusively for log messages that are read by end-users. However, I seri-
ously doubt that there is much of a consensus on this point. For example, I use
Level.INFO when redirecting standard output to the log file in a GUI applica-
tion and Level.WARNING when redirecting standard error. On the other hand,
programmers should be thinking of the end-user when logging messages. The
API docs for the java.util.logging package includes a very well written
overview, part of which I think programmers should keep in mind when selecting
a log level. Otherwise, you tend to forget that this is not the same as printing to
standard output or standard error. Here then is the part of that overview which
should help you to remember that log messages have a much broader audience:
The central goal of the logging APIs is to support maintaining and ser-
vicing software at customer sites.

There are four main target uses of the logs:

1. Problem diagnosis by end users and system adminis-


trators . This consists of simple logging of common prob-
lems that can be fixed or tracked locally, such as running out
of resources, security failures, and simple configuration
errors.

940 JAVA RULES


2. Problem diagnosis by field service engineers. The log-
ging information used by field service engineers may be con-
siderably more complex and verbose than that required by
system administrators. Typically such information will require
extra logging within particular subsystems.
3. Problem diagnosis by the development organization.
When a problem occurs in the field, it may be necessary to
return the captured logging information to the original devel-
opment team for diagnosis. This logging information may be
extremely detailed and fairly inscrutable. Such information
might include detailed tracing on the internal execution of
particular subsystems.
4. Problem diagnosis by developers. The Logging APIs may
also be used to help debug an application under develop-
ment. This may include logging information generated by the
target application as well as logging information generated
by lower-level libraries. Note however that while this use is
perfectly reasonable, the logging APIs are not intended to
replace the normal debugging and profiling tools that may
already exist in the development environment.
The logging of uncaught exceptions is discussed in 6.10.1 Top-level Exception
Handlers, for example, may initially result in a review of the log report by the sys-
tem administrator, baffle field service engineers, and only then be “returned” to
the responsible development team for detailed analysis.
Several of the logging methods in Table 6.8 Logging Methods on page 976
include parameters for passing objects. Those objects are then converted into
strings (by invoking their toString() method) and included in the log mes-
sage. The main method in the example above includes one of those methods:

logger.entering("Test", "main(String[] args)", args);

Note that although args was logged, nothing printed. That is because I did not
use any command-line arguments when launching the Test program. Had I
used “testing testing testing” after the program name on the DOS command-line,
the second line would have printed as follows.

FINER: ENTRY testing testing testing

ASSERTIONS, EXCEPTIONS, AND LOGGING 941


The logging API surprisingly diverges from the string conversion policy estab-
lished for the rest of the core API in this regard; null values are not converted
into the "null" string. They simply do not print.
There is no method that when invoked prints the log report. A logger creates
log records which are then “exported” or “published” (depending on which API
documentation comment you are reading) by handlers as soon as the log
records are created. As stated in the API docs for the Handler class:
A Handler object takes log messages from a Logger and exports
them. It might for example, write them to a console or write them to a
file, or send them to a network logging service, or forward them to an
OS log, or whatever.215

A logger without handlers is useless. It creates log records that are discarded
no sooner than they are created. For example, try commenting out the following
line of code in the configureLogger() method.

logger.addHandler(handler);

Nothing is printed as a result. There is actually an exception to the rule that there
is no method that when invoked prints a log report. The push() method in
MemoryHandler sometimes publishes an entire log report by exporting all of
the log records in memory to some other kind of handler. There is an example of
using a memory handler below.
The logger.setUseParentHandlers(false) method invocation
in this example is particularly important. Try commenting out that line of code
and then adding the following one to the main method.

logger.severe("testing, testing, testing");

The output now includes four additional lines similar to the following.

Aug 28, 2002 12:16:12 PM Test main


SEVERE: testing, testing, testing
Aug 28, 2002 12:16:12 PM Test main
SEVERE: testing, testing, testing

215. API docs for the java.util.logging.Handler class.

942 JAVA RULES


This is the same log record printed twice. All loggers except the root logger
have a parent. In the case of an arbitrarily named logger such as "unnamed"
the default parent is the root logger. Those parent loggers have handlers that
also publish the log record unless setUseParentHandlers(false) is
invoked.
Handlers added to the root logger are referred to as global handlers. I
really do not like this term. These are best thought of as handlers for the root
logger, especially because they are no longer “global” if setUseParent-
Handlers(false) is invoked. In an unedited logger configuration file, there
is only one global handler. It is a console handler set to Level.INFO, which is
consistent with the default logging level of the root logger. In the example above,
the “global” console handler added to the root logger is publishing the same
SEVERE log record. The only reason why the other log records in this example
are not printed twice is their level.
The default configuration does not include a global file handler, but adding
one is a simple matter of commenting out one line in the default logging configu-
ration file and uncommenting another. Here are the relevant lines from the
default logging.properties file:
###################################################
# Global properties
###################################################

# "handlers" specifies a comma separated list of log Handler


# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.


#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

Logging configuration files are discussed in 6.11.2 Logging Configuration. Think


twice before using a global file handler, however. It is commented out for a rea-
son. If you add your own file handler to a logger and forget to invoke setUse-

ASSERTIONS, EXCEPTIONS, AND LOGGING 943


ParentHandlers(false) , two files are inexplicably written to your hard-
drive. Depending on the pattern used to create the second file handler, this extra
copy of the log report may or may not be in the same directory. For example,

import java.util.logging.*;
public class Test {
public static void main(String[] args)
throws java.io.IOException {
Logger logger = Logger.getLogger("unnamed");
logger.addHandler(new FileHandler("%h/java%g.log"));
logger.severe("testing, testing, testing");
System.out.println("User’s home directory = " +
System.getProperty("user.home"));
}
}

Because the pattern string passed to the FileHandler constructor in this


example begins with the same %h as the file handler used by the root logger,
there are two files created in the user’s home directory (java0.log and
java1.log ). This problem is addressed in Bug Ids 4362603 and 4372093.
The evaluation of the later begins as follows.
This is an interesting one. However, after studying it carefully I believe
the Logging APIs are behaving correctly.216

This shows that even a Sun engineer had difficulty figuring out what was going
on. I did to, and so will almost every other programmer learning how to
use the logging API for the first time. That is really a shame because it seri-
ously detracts from an otherwise very easy to learn API. Note that the default
pattern string for the global file handler in the logging configuration file is "%h/
java%u.log" (not %g as used in the bug reports), but the same problem
occurs anyway. In the example above, an extra file is created by the no-argu-
ment FileHandler() constructor because it uses the exact same pattern
string as the global file handler (the one in the logging configuration file).
There are three general purpose handlers in the java.util.logging
package: ConsoleHandler, FileHandler, and SocketHandler. In

216. Evaluation of Bug Id 4372093.

944 JAVA RULES


addition, there is a MemoryHandler that is typically used in conjunction with a
FileHandler. Figure 6.8 illustrates this configuration. A memory handler is a

Figure 6.8 Illustration of the configuration used by an example in this section

circular buffer (sometimes referred to as a cyclic or ring buffer) that begins dis-
carding the oldest log records once it is full in order to make room for new ones.
The benefit of doing so is twofold:
• First and foremost the log records in a memory buffer are not formatted
(including localization), which means that logging them is a lot less expen-
sive than would be if the log records were published directly to a file.
• The other major benefit of using a memory handler is that only the most
recent log records are written to the file. (Those are also the most relevant
log records in the event of a failure.) This saves wasting a lot of disc space
for log records that nobody will ever read.
It is important to understand the design of memory handlers. The log records
in a memory handler may never be published. The whole idea is that the log

ASSERTIONS, EXCEPTIONS, AND LOGGING 945


records are only published if something goes wrong. This is determined by the
push level passed to a MemoryHandler constructor. Typically the push level
is Level.SEVERE (as in the example below). Thus unless something very seri-
ous happens, the application will never incur the cost of formatting log records.
Memory handlers should always be used in performance critical applications.
Note that although memory buffers use what is described as a circular
buffer, invoking the close() method in the MemoryHandler class does not
invoke the push() method as might be expected. Doing so would be contrary
to the design of memory handlers.
Here is an example of how to use the configuration in Figure 6.8:

import java.io.*;
import java.util.logging.*;
class Test {
private static final String CLASS = "Test";
private static Logger logger = configureLogger();
private static MemoryHandler memoryHandler;
public static void main(String[] args) throws IOException {
new Test().copy(new File("Test.java"),
new File("Copy of Test.java"));
}
public static void copy(File source, File destination)
throws IOException {
final String METHOD =
"copy(File source, File destination)";
logger.entering(CLASS, METHOD,
new Object[]{ source, destination });
byte[] buffer = new byte[512];
int numBytes = 0;
FileInputStream in = new FileInputStream(source);
FileOutputStream out = new FileOutputStream(destination);
try {
while ((numBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, numBytes);
}
} catch(IOException e) {
logger.throwing(CLASS, METHOD, e);
throw e;
}
finally {
in.close();

946 JAVA RULES


out.close();
}
logger.exiting(CLASS, METHOD);
}
private static Logger configureLogger() {
Logger logger = Logger.getLogger("com.javarules");
logger.setLevel(Level.ALL);
FileHandler target = null;
try {
target = new FileHandler("%t/javarules.log",200000,1);
target.setFormatter(new SimpleFormatter());
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
memoryHandler = new MemoryHandler(target,
2000,
Level.SEVERE);
logger.addHandler(memoryHandler);
logger.setUseParentHandlers(false);
return logger;
}
}

In the nomenclature of the logging API, the file that a memory handler writes to is
referred to as the target (or target handler). Log files typically use the .log
file extension. This log file is being written to the directory that the host operat-
ing system uses to write temporary files. File handlers default to Level.ALL,
so there’s no reason to set their level (at least not in this example). File-
Handler constructors throw IOException which requires the use of a try
block because class variable initializers cannot throw checked exceptions. Inas-
much as the application has just started, exiting is a reasonable response.
Remember, too, that try blocks are never definite assignment, so target
must be initialized to null when declared.
The relationship between the memory handler size (the number of log
records written before the memory handler starts discarding old ones) and the
file handler limit (the maximum number of bytes that can be written to any
one file) is significant. The most recent log records are written first, so if the file
is too small to hold all of the records in a memory handler the older log records

ASSERTIONS, EXCEPTIONS, AND LOGGING 947


are effectively truncated. This is undesirable because the memory handler size is
explicitly set. It implies that the two setting are out of sync. My suggestion (in
part because it is easy to remember) is to set the file handler limit to one hun-
dred times the memory handler size. In this example, the memory handler can
hold up to 2,000 log records, so the file handler limit (using a single file) is
set to 200 KB. This allows for log records that are on average 100 bytes in
length after character conversion. This is appropriate for one byte character
encoding schemes such as ASCII and Latin-1. If using an XML formatter, the
memory handler size should be multiplied by three hundred to arrive at an
approximate file handler limit (or log file size).
The string argument passed to a file handler is referred to as a (file name)
pattern. In file handler patterns, % is the escape character. In this example, the
%t escape sequence is used to write the log file to the system temporary direc-
tory (which is the same as the "java.io.tmpdir" system property). An
alternative is %g which writes the log file to the user’s home directory (which is
the same as the "user.home" system property). Otherwise absolute or rela-
tive (to the current working directory) pathnames are used. The FileHandler
class does not create non-existent directories in a pathname. At present, it
throws an IOException instead with a detail message of "Couldn't get
lock for" + pattern. So if you are going to use a pathname such as
"logs/java%g.log" , make sure the logs subdirectory already exists.
Table 6.5 lists all of the escape sequences (referred to as “special compo-
nents”217 in the API docs) that can be used in the pattern strings passed to a file
handler.
One very common alternative “to distinguish rotated logs” is to embed the
date and time a log file is created in the file name. For example, a typical log file
name might be javarules20020910202343.log . This is actually much
more intuitive than the use of generation numbers. The RFE for this RFE to the
FileHandler class is Bug Id 4532403, “Package logging: DateTimeStamp
Pattern in FileHandler.”

217. API docs for the java.util.logging.FileHandler class.

948 JAVA RULES


Table 6.5 Escape Sequences in the FileHandler Class
Escape
Sequence Meaning

"/" A platform-neutral pathname separator. Note that a pathname separator is


required after %t and %h . If writing to a directory relative to the current
working directory (for example, "logs/java%u.log"), do not begin
the pattern string with a pathname separator. If you mistakenly use "\"
instead of "/" as a pathname separator, the compiler generates an “illegal
escape character” error message, but "\\" can be used (at least on
Win32 systems)

"%t" The system temporary directory, which is the same as


System.getProperty("java.io.tmpdir")
"%h" The user’s home directory, which is the same as
System.getProperty("user.home")
"%g" A generation number used “to distinguish rotated logs.”a The values range
from 0 to count minus one (the same as an index value), where count
is a FileHandler property discussed in Table 6.7 Specialized
Handler Properties on page 973. The most recently created log file has a
zero generation number. All other log files with the same name (except of
course the generation number) have their generation number incremented
until either there is either a gap in the sequence or else count minus one
is reached. If count minus one is reached, then that file is deleted. So for
example, if count is ten, the log file name is java%g.log, and all ten
files are present, then instead of renaming java9.log it is deleted and
java8.log becomes the new java9.log, and so on. All of this is
done without updating the file attributes so that the date and time the file
was actual created (versus being merely renamed) are preserved.

ASSERTIONS, EXCEPTIONS, AND LOGGING 949


Table 6.5 Escape Sequences in the FileHandler Class (Continued)
Escape
Sequence Meaning

"%u" A unique number used “to resolve conflicts.” This escape sequence is very
easy to misunderstand. It is almost always replaced with a zero. The only time
%u is anything other than a zero is if the FileHandler class cannot
lock the requested file name. In other words, the file is already in use. This
rarely happens. If the file name already exists (using 0 as a replacement for
%u), that is an entirely different matter. The problem of pre-existing file
names is always resolved with the %g escape sequence.

"%%" As with all escape characters, two of them in a row is an escape sequence
that translates into the escape character (without any further translations),
so "%%" translates to a single percent sign "%" that is used in the log file
name.
a. API docs for the java.util.logging.FileHandler class

The system temporary directory is not always easy to find, which can be a
vexing problem because the file search utility on some operating systems
does not include the system temporary directory when searching for a file
or folder. If you run into the problem of not being able to find log files created
using %t execute the following two lines of code.
String tmpdir = System.getProperty("java.io.tmpdir");
System.out.println(new File(tmpdir).getCanonicalPath());

The output will tell you precisely where the %t directory is located. Note that the
API docs suggest that the system temporary directory on Microsoft Windows is
C:\TEMP, which can be grossly misleading. On my computer, for example, the
system temporary directory is C:\Documents and Settings\Douglas
K. Dunn\Local Settings\Temp.
The remainder of this section discusses the global logger, a reference to
which is stored in the public Logger.global field. The idea behind this
logger is to make using loggers as easy as the unnamed package or type-
import-on-demand import declarations. These are all devices used while doing
“casual” development work. As stated in the API docs:

950 JAVA RULES


The "global" Logger object is provided as a convenience to
developers who are making casual use of the Logging [sic] pack-
age.218

In my opinion, however, Logger.global is worse than useless because it


can easily confuse and confound programmers learning how to use the logging
API. (Even the name is confusing. The “global” handlers discussed above (a con-
sole handler by default and possibly a file handler) are added to the root log-
ger (which is the truly global logger), not to Logger.global.) One
naturally assumes that the global logger is ready to use and does not require
any configuration. That simply is not true. Why? Because most of the conve-
nience methods (ten out of the thirteen) log trace and configuration information.
The problem is that these are the very methods a beginner is most likely to use.
Trace information in particular (as in the example in this section) is a logical
place to start logging. The global logger, however, will not log those mes-
sages because it has a default level of Level.INFO (assuming an unmodi-
fied logger configuration file). This is at a bare minimum a documentation
problem that should be addressed in the API docs for Logger.global. A bet-
ter fix would be to more thoughtfully configure the global logger.
The reality is that the global logger either has to be configured or else
changes have to be made in the logger configuration file. Both of those choices
are conceptual hurdles that should not be placed in front of a beginner. Here are
two alternatives for configuring the global logger so that it logs all messages:

private static Logger configureLogger() {


Logger logger = Logger.global;
Handler handler = new ConsoleHandler();
logger.addHandler(handler);
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
return logger;
}

private static Logger configureLogger() {

218. API docs for the global field in the java.util.logging.Logger class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 951


Logger logger = Logger.global.getParent();
logger.setLevel(Level.ALL);
Handler[] handlers = logger.getHandlers();
for (int i=0; i<handlers.length; i++)
handlers[i].setLevel(Level.ALL);
return Logger.global;
}

Except for the line of code in bold, the first configureLogger() method is
the same as the first example (that used an arbitrarily named logger) at the start
of this section. The second one is a nightmare. The other alternative is to edit
the default logger configuration file (changing only the .level= INFO entry).
This may sound easy to some readers, but programmers eager to learn the log-
ging API may not have any experience editing configuration files. Indeed, they
may be very reluctant to do so.
I therefore recommend that someone new to the logging API stay away from
the global logger. As stated in the API docs, “The global logger is initialized
by calling Logger.getLogger("global").”218 In other words, it is has an
arbitrary name. As with all loggers that have arbitrary names, the root logger is
the parent. Invoking Logger.getLogger("unnamed") during casual
development accomplishes the same thing, is more in line with how loggers are
actually used, and avoids the assumption that the global logger can be used to
log trace and configuration information without being reconfigured. As stated
above, the meaning of “unnamed” is “a logger for the unnamed package.” Do
not pass "" (an empty string) because that is the name of the root logger.
The significance of logger names is discussed further in the following subsec-
tion.

6.11.1 The Logger Namespace


While trying to decide on a name for this section, I became amused at the num-
ber of different names used in the API docs and the “Java Logging Overview”
document. It really is astounding. Here is the list I compiled:
• A hierarchical dot-separated namespace
• A hierarchical namespace

952 JAVA RULES


• A hierarchical namespace of named Loggers
• A hierarchical namespace of Logger objects
• A naming hierarchy
• The Logger namespace
• The logging namespace
• The LogManager namespace
• The LogManager global namespace
• The namespace
• The shared namespace
• The tree
I suppose it is even possible that I missed one or two. All of these names refer to
the same thing—a tree data structure used in the implementation of the Log-
Manager class. I decided on the name logger namespace because the
objects stored in this tree are instances of the Logger class. As with all tree
data structures, nodes are referred to as parents and children (though
mother and daughter are also common). You will also find sentences such as
these in the API docs and “Java Logging Overview” document:
Each Logger keeps track of a “parent” Logger, which is its nearest
existing ancestor in the Logger namespace.219

If the new level is null, it means that this node should inherit its level
from its nearest ancestor with a specific (non-null) level value.220

A logger's parent is its nearest extant ancestor in the logging name-


space.221

Thus ancestor is a synonym for parent. The root node at the top of a tree is
special because it has no parent, only is this case it is referred to as the root
logger.

219. API docs for the java.util.logging.Logger class.


220. API docs for the setLevel(Level newLevel) method in the
java.util.logging.Logger class
221. Unascribed, “Java Logging Overview” document in the 1.4.1 release, §1.3, “Loggers.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 953


All loggers are either named or anonymous depending on which of the fol-
lowing factory methods are invoked.

public static Logger getLogger(String name)


public static Logger getLogger(String name,
String resourceBundleName)

public static Logger getAnonymousLogger()


public static Logger getAnonymousLogger(String resourceBundleName)

If using either of the first two methods you really have no choice but to name the
logger because passing a null reference throws a NullPointer-
Exception and passing "" (an empty string) returns a reference to the root
logger. Anonymous loggers are “primarily intended for use from applets.”222
Besides not having a name, anonymous loggers are special for three reasons:
• Unlike the getLogger methods, the getAnonymousLogger meth-
ods return a new logger every time they are invoked. Furthermore, the
Logger class does not store a reference to the returned logger in the log-
ger namespace. Thus, only the client programmer who invokes one of the
getAnonymousLogger factory methods has access to the logger.
• Because access to the logger is strictly controlled by the client who invokes
the getAnonymousLogger method, no permission is required to config-
ure an anonymous logger. This makes it possible for untrusted applet code
to invoke the following methods in the Logger class.
setFilter(Filter newFilter)
setLevel(Level newLevel)
addHandler(Handler handler)
removeHandler(Handler handler)
setUseParentHandlers(boolean useParentHandlers)
These are all of the methods in the Logger class used to configure loggers.
• “Anonymous” loggers are not “autonomous.” They have a parent logger,
which is always the root logger. This means their log records may be pub-
lished by the so-called “global” handlers unless setUseParentHand-

222. API docs for the getAnonymousLogger() method in the


java.util.logging.Logger class.

954 JAVA RULES


lers(false) is invoked. It also means they may inherit the level of the
root logger.
Because there are no security manager checks for anonymous loggers they
should only be used by applets. All other applications should use an arbitrarily
named logger instead of an anonymous logger when needed. That way the secu-
rity mechanism is no circumvented.
Only named loggers are stored in the logger namespace. The logger name-
space serves three very distinct purposes, which are listed here in their order of
importance:
1. Logger Sharing: Logger sharing explains why the Logger class has no
public constructors. Loggers are shared between the different classes in
a package by invoking getLogger("package name"), which either
creates a logger for that package or returns a reference to a previously
“registered” logger with the same name. In order for this to work there must
be an agreed upon naming convention, which is to use package names
exactly as they would appear in an import declaration (fully qualified and
“dot separated”). What the API docs do not explicitly say is that in practice
loggers are typically shared across subpackages. For example, the logger
used by classes in the java.awt package should be named
"java.awt". That includes a lot of subpackages. I think this is why the
API docs for the getLogger factory methods consistently refer to the
name parameter as a “subsystem.” That is somewhat misleading, however,
because loggers are also created for application programs, subpackages,
and individual classes. The creation of loggers for subpackages and individ-
ual classes is usually a means of intercepting log records during develop-
ment and unit testing so that they can be written to a separate file.
2. The Publication of Log Records: By default log records are published not
only by the handlers of the target logger but also by all of the handlers of
parent loggers in the logger namespace up to and including the root logger.
There are two methods in the Logger class used to control this behavior:
public void setUseParentHandlers(boolean useParentHandlers)
public boolean getUseParentHandlers()

Packages (or rather subsystems) such as java.awt must assume that


the root logger is being used by an application program. For example, an
application program could configure the root logger to use a Socket-

ASSERTIONS, EXCEPTIONS, AND LOGGING 955


Handler with an XMLFormatter to transmit SEVERE messages
across a network (much like the Error Reporting function in Windows XP).
Therefore they should not attempt to configure the root logger.
3. Inheriting the Level and Resource Bundle Name of a Parent Logger:
Child loggers inherit the level and resource bundle name of their parent
loggers. If the level is neither set programmatically by invoking the
setLevel(Level newLevel) method nor as the result of there
being a .level property for that particular logger, the level is set to
null. Likewise, unless the getLogger(String name, String
resourceBundleName) factory method is invoked to create the log-
ger, the resource bundle is also set to null. In both cases this means the
level or resource bundle is inherited from a parent logger. This explains
why the default logging configuration file refers to the level of the root
logger (the .level= entry) as the default (global) logging level. There
will always be a level to inherit, but the root logger does not have a resource
bundle name. Therefore, loggers do not inherit a resource bundle name
from the root logger. There is no setResourceBundleName(String
name) method in the Logger class, so the fact that the root logger has
no resource bundle cannot be changed.
The logger sharing aspect of the logger namespace does not mean that all
loggers have to be named after the packages in which they are used. As stated
in the “Java Logging Overview” document:
The namespace should typically be aligned with the Java packaging
namespace, but is not required to follow it slavishly. For example, a
Logger called "java.awt" might handle logging requests for
classes in the java.awt package, but it might also handle logging
for classes in sun.awt that support the client-visible abstractions
defined in the java.awt package.223

Loggers can also have arbitrary names such as the global logger which is
named "global" or the "unnamed" logger used in the examples in this
chapter. Using an arbitrarily named logger simply means that the logger is
sure not to have any children and that the root logger will be the parent. They
are not much different than anonymous loggers is this regard.

223. Unascribed, “Java Logging Overview” document in the 1.4.1 release, §1.3, “Loggers.”

956 JAVA RULES


The API docs say that a logger “send its output”224 to parent loggers, but
the “Java Logging Overview” document says that loggers “inherit”225 the han-
dlers of their parents. Conceptually these are two very different things, so I
looked up the code in the Logger class. It is a simple while loop in the
log(LogRecord record) method that looks like this:

Logger logger = this;


while (logger != null) {
Handler targets[] = logger.getHandlers();

if (targets != null) {
for (int i=0; i<targets.length; i++) {
targets[i].publish(record);
}
}

if (!logger.getUseParentHandlers()) {
break;
}

logger = logger.getParent();
}

As you can clearly see there is nothing in this code that would justify
saying that a logger “inherits” handlers from its parent loggers. The sim-
ple fact of the matter is that log records are logged by the target logger and
then by the parent loggers, unless setUseParentHandlers(false) is
invoked. This while loop is interesting in that you can see how getUse-
ParentHandlers() is used to break this chain of logging to parent loggers.

6.11.2 Logging Configuration


If the logging API has a clean interface, part of the reason is because the dirt of
logging configuration has been swept under the rug. The single biggest concep-
tual problem with the logging API is the mistaken idea that the LogManager
class uses logging configuration files to automatically configure loggers and han-

224. API docs for the setUseParentHandlers(boolean useParentHandlers) and


getUseParentHandlers() method in the java.util.logging.Logger class.
225. Unascribed, “Java Logging Overview” document in the 1.4.1 release, §1.3, “Loggers.”

ASSERTIONS, EXCEPTIONS, AND LOGGING 957


dlers. The LogManager class uses a logging configuration file to accomplish
three clearly discernible tasks, which are listed here in their order of importance:
1. Load properties into a private Properties object that application pro-
grammers can subsequently access using the getProperty(String
name) method in LogManager. This method is to logging control
properties what System.getProperty(String key) is to system
properties. The term logging control properties is from the API docs.226
2. Those properties (or else the documented defaults) are then used to config-
ure the root logger as well as the “global” handlers that are used by the root
logger
3. Finally, whenever a logger is created the Properties object is checked
to see if a key exists for the logger name + ".limit". If so, then the log-
ger is set to that level, but that is the extent to which the LogManager
class uses the logging configuration file to configure loggers other than the
root logger. This is an arbitrarily limited amount of logging configura-
tion, especially when you consider that the root logger and global handlers
are fully configured using the default logging configuration file. It is perfectly
natural for programmers to expect that other loggers and handlers can be
statically configured as easily as the root logger and global handlers. The
reality, however, is that there is no support whatsoever for doing so. What
makes this particularly troubling is that all of the code is written. It is being
used to configure the root logger and global handlers and could very easily
be adapted to configure other loggers and handlers.
As stated in the API docs, the log manager has two primary functions. One is to
manage the logger namespace, and the other is described as follows.
Manages a set of logging control properties. These are simple key-
value pairs that can be used by Handlers and other logging objects to
configure themselves.227 [emphasis added]

You can also see this in the default logging configuration file. Reduced to it’s
essence, the default logger configuration file looks like this:

226. API docs for the java.util.logging.LogManager class.


227. API docs for the java.util.logging.LogManager class.

958 JAVA RULES


handlers= java.util.logging.ConsoleHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.FileHandler.pattern = %h/java%g.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter

All nine of these are properties used to configure the root logger. It is important
to understand that initial logging configuration is limited to the above list of
three very specific items. Otherwise you fail to grasp that the design of the
LogManager class is to load logging control properties, not to configure
loggers (other than the root logger).
The API docs for the LogManager class list three, mutually exclusive
options for loading logging control properties. To that list I have added a forth
for loading logging control properties from a JAR file:
• Loading From a Database or Across a Network: The name of any class
with a no-argument constructor can be passed to the LogManager class
using the java.util.logging.config.class system property.
That class then becomes the logging configuration class. The logging
configuration class uses the same
readConfiguration(InputStream ins) method as is used to
load logging control properties from a JAR file. The idea behind this class is
simply that the logging control properties may not be stored in a directory in
the local filesystem. For example, the logging configuration class can use
JDBC to load logging control properties from a database, or JNDI to load
them from a LDAP directory service. This must be a public class on the
bootstrap classpath (which is accomplished using the non-standard -
Xbootclasspath/a:path launcher option). The no-argument con-
structor must also be public and cannot have a throws clause. If for
any reason whatsoever this class fails to load or if the constructor

ASSERTIONS, EXCEPTIONS, AND LOGGING 959


throws a runtime exception or error, the logging configuration file sys-
tem property will be tried next.
• Loading from Anywhere in the Local File System: The name of a logger
configuration file can be passed as an absolute or relative pathname to the
LogManager class using the java.util.logging.config.file
system property. That file then becomes the logging configuration file,
and is used instead of the default logging configuration file. (Note that the
term is logging configuration file, not logger configuration file, because it is
used to configure both loggers and handlers.) Essentially this is just a mech-
anism for using a different file name, storing the file in a different directory
from the default logging configuration file, or both. It does not work for
JAR files, however.
• Loading From a JAR File: Unlike class loaders, the FileInputStream
class used to read logging configuration files does not know how to read
from JAR files. To configure loggers from JAR file, you must think of the log-
ger configuration file as just another resource. The following line of code,
where loader is a reference to the same class loader that loaded the JAR
file, does the trick.
LogManager.getLogManager().readConfiguration(
loader.getResourceAsStream("logging.properties"));

Note that there is no need to invoke the reset() method in Log-


Manager. The readConfiguration method does that for you.
• Loading The Default Logger Configuration File: The default logging
configuration file is named logging.properties. The precise loca-
tion of the file depends on whether a JRE or SDK is being used. If using a
JRE, then the logging.properties file is in the lib directory. If using
a SDK, then it is in the jre\lib directory. If for any reason you are unsure
as to which logging.properties file is being used, change the line
that reads .level= INFO (the “default global logging level”) to read
.level= test and then compile any program. The following message
should be printed to standard error.
Bad level value for property: .level

If you forget to change it back the default global logging level (used when
the logging configuration file has a bogus value) is INFO, so no harm is
done.

960 JAVA RULES


The two system properties mentioned in this list can be set using the -D
launcher option. The java.util.logging.config.class property must
be used along with -Xbootclasspath/a:path, which makes for some
rather long DOS commands. For example, while writing this section I had to use
the following DOS command.

C:\Java\classes>java -Djava.util.logging.config.class=LoggingConf
iguration -Xbootclasspath/a:C\Java\classes Test

The API docs for LogManager includes the following sentence.


These two properties may be set via the Preferences API, or as com-
mand line property definitions to the “java” command, or as system
property definitions passed to JNI_CreateJavaVM.228

As documented in Bug Id 4691587, however, the Preference API functionality


has yet to be implemented.
Logging configuration files are said to be “in java.util.Properties
format,”229 which is specified in the API docs for the load(InputStream
inStream) method in the Properties class. If you are not already familiar
with this format, I strongly recommend that you take the time to stop and read it
now. As always, be very careful making changes in a properties file. If you
have not done so before, it may be prudent to save the original under a different
name. If opening a .properties file for the first time, use a simple text editor
such as WordPad on Microsoft Windows.
I very deliberately started this section with a focus on logging control proper-
ties as a way of counteracting the notion that logging configuration files can be
used to configure loggers other than the root logger. The logging API documen-
tation (including the “Java Logging Overview” document) is largely to blame for
this misperception. For example,
The Logging APIs offer both static and dynamic configuration control.
Static control enables field service staff to set up a particular configura-
tion and then re-launch the application with the new logging settings.

228. Ibid.
229. Ibid.

ASSERTIONS, EXCEPTIONS, AND LOGGING 961


Dynamic control allows for updates to the logging configuration within a
currently running program.230

I find this quote from the API docs for the LogManager class to be very mis-
leading. The so-called “static configuration control” should not be compared with
the constructors and set methods in the logging API. It is nothing more than a
reference to the logging control properties loaded during initial logger configura-
tion. Actually using those properties to configure a logger takes a lot of code,
code that programmers rather naturally assume is implemented for them. Not
so. It took me the better part of two days just to develop and test the following
utility class that configures handlers based on logging control properties.

/**
* This is an experimental class in that I am not using any in-
* dentation for entities declared in the class body (e.g. fields
* and methods). It is always a healthy thing for programmers to
* question the status quo. Why lose all that space when there is
* usually only one class per compilation unit anyway? It doesn’t
* make any sense.
*/
import java.util.logging.*;
public class Loggers {

private Loggers() { } //utility class

static LogManager manager = LogManager.getLogManager();


static ClassLoader loader = ClassLoader.getSystemClassLoader();

public static void configureStreamHandler(Logger logger){


String key = logger.getName() + ".StreamHandler";
StreamHandler handler = new StreamHandler();
checkForOptionalProperties(key, handler);
logger.addHandler(handler);
}

public static void configureFileHandler(Logger logger) {


String key = logger.getName() + ".FileHandler";
String pattern = getProperty(key + ".pattern");
int limit = getIntegerProperty(key + ".limit");

230. Ibid.

962 JAVA RULES


int count = getIntegerProperty(key + ".count");
if (limit < 0) {
throw new LoggingPropertiesException(key + ".limit",
Integer.toString(limit));
}
if (count < 1) {
throw new LoggingPropertiesException(key + ".count",
Integer.toString(count));
}
boolean append = getBooleanProperty(key + ".append");
FileHandler handler;
try {
handler = new FileHandler(pattern,limit,count,append);
} catch (Exception e) {
throw new LoggingPropertiesException(key +
".pattern", pattern, e);
}
checkForOptionalProperties(key, handler);
logger.addHandler(handler);
}

public static void configureSocketHandler(Logger logger) {


String key = logger.getName() + ".SocketHandler";
String host = getProperty(key + ".host");
int port = getIntegerProperty(key + ".port");
SocketHandler handler;
try {
handler = new SocketHandler(host, port);
} catch (Exception e) {
throw new LoggingPropertiesException(key + ".host",
host,
(new LoggingPropertiesException(key + ".port",
Integer.toString(port), e)));
}
checkForOptionalProperties(key, handler);
logger.addHandler(handler);
}

public static void configureConsoleHandler(Logger logger){


String key = logger.getName() + ".ConsoleHandler";
ConsoleHandler handler = new ConsoleHandler();
checkForOptionalProperties(key, handler);
logger.addHandler(handler);
}

ASSERTIONS, EXCEPTIONS, AND LOGGING 963


public static void configureMemoryHandler(Logger logger) {
String key = logger.getName() + ".MemoryHandler";
String value = getProperty(key + ".target");
Handler target;
try {
target=(Handler)loader.loadClass(value).newInstance();
} catch (Exception e) {
throw new LoggingPropertiesException(key +
".target", value, e);
}
int size = getIntegerProperty(key + ".size");
if (size <= 0) {
throw new LoggingPropertiesException(key + ".size",
Integer.toString(size));
}
Level pushLevel = getLevelProperty(key + ".push");
MemoryHandler handler = new MemoryHandler(target,
size,
pushLevel);
checkForOptionalProperties(key, handler);
logger.addHandler(handler);
}

private static void checkForOptionalProperties(String key,


Handler handler) {
String level = key + ".level";
if (propertyExists(level))
handler.setLevel(getLevelProperty(level));
String filter = key + ".filter";
if (propertyExists(filter))
handler.setFilter(getFilterProperty(filter));
String formatter = key + ".formatter";
if (propertyExists(formatter))
handler.setFormatter(getFormatterProperty(formatter));
String encoding = key + ".encoding";
if (propertyExists(encoding)) {
String value = getProperty(key + ".encoding");
try {
handler.setEncoding(value);
} catch (Exception e) {
throw new LoggingPropertiesException(encoding,
value, e);
}

964 JAVA RULES


}
}

private static int getIntegerProperty(String name) {


String value = getProperty(name);
try {
return Integer.parseInt(value);
} catch (Exception e) {
throw new LoggingPropertiesException(name, value, e);
}
}

private static boolean getBooleanProperty(String name) {


String value = getProperty(name);
value = value.toLowerCase();
if (value.equals("true") || value.equals("1")) {
return true;
} else if (value.equals("false") || value.equals("0")) {
return false;
}
throw new LoggingPropertiesException(name, value);
}

private static Level getLevelProperty(String name) {


String value = getProperty(name);
try {
return Level.parse(value);
} catch (Exception e) {
throw new LoggingPropertiesException(name, value, e);
}
}

private static Filter getFilterProperty(String name) {


String value = getProperty(name);
try {
return (Filter)loader.loadClass(value).newInstance();
} catch (Exception e) {
throw new LoggingPropertiesException(name, value, e);
}
}

private static Formatter getFormatterProperty(String name) {


String value = getProperty(name);
try {

ASSERTIONS, EXCEPTIONS, AND LOGGING 965


return (Formatter)loader.loadClass(value)
.newInstance();
} catch (Exception e) {
throw new LoggingPropertiesException(name, value, e);
}
}

private static boolean propertyExists(String name) {


String value = manager.getProperty(name);
if (value == null || value.length() == 0)
return false;
return true;
}

private static String getProperty(String name) {


String value = manager.getProperty(name);
if (value == null || value.length() == 0)
throw new LoggingPropertiesException(name, value);
return trim(value);
}

/**
* It is not at all clear to me if an internationalized trim()
* method is really necessary when editing .properties files.
*/
public static String trim(String s) {
int length = s.length();
int index = 0;
while (index < length &&
Character.isWhitespace(s.charAt(index))) {
index++;
}
while (index < length &&
Character.isWhitespace(s.charAt(length - 1))) {
length--;
}
return (index > 0 || length < s.length()) ?
s.substring(index, length) : s;
}
} //END OF PUBLIC CLASS

class LoggingPropertiesException extends RuntimeException {


public String propertyName;
public String value;

966 JAVA RULES


public LoggingPropertiesException(String propertyName,
String value) {
super();
this.propertyName = propertyName;
this.value = value;
}
public LoggingPropertiesException(String propertyName,
String value,
Throwable cause) {
super(cause);
this.propertyName = propertyName;
this.value = value;
}
public String getMessage() {
return propertyName + " = " + value;
}
}

The design of this utility class is very straightforward. Handler properties are
divided into two very distinct groups. One group must be passed to constructors
because they have no corresponding set methods (the pushLevel in a
MemoryHandler being the only exception). The utility class requires that
these properties be defined in the logging configuration file else a runtime
exception is thrown. The other group of handler properties (of which there are
only four) are optional because they have set methods. Adding handlers to a log-
ger using this utility class is as simple as invoking

logger = Logger.getLogger("com.javarules");
Loggers.configureConsoleHandler(logger);
Loggers.configureFileHandler(logger);

The property keys are a combination of the logger name followed by the simple
name of the handler class. For example,

com.javarules.FileHandler.pattern = logs/java%g.log
com.javarules.FileHandler.limit = 0
com.javarules.FileHandler.count = 30
com.javarules.FileHandler.append = false
com.javarules.FileHandler.level = ALL
com.javarules.FileHandler.formatter =
java.util.logging.SimpleFormatter

ASSERTIONS, EXCEPTIONS, AND LOGGING 967


com.javarules.ConsoleHandler.level = WARNING

Note that this property naming convention is significantly different from what is
described in the API docs for the LogManager class:
The properties for loggers and Handlers will have names starting with
the dot-separated name for the handler or logger.231

Handlers do not actually have names. They have a type.


One place where the static configuration of handlers gets really tricky is the
target of a memory handler. Here is the simplest solution to the problem:

java.util.logging.FileHandler.pattern = %h/java%g.log
java.util.logging.FileHandler.count = 5
java.util.logging.FileHandler.formatter = \
java.util.logging.SimpleFormatter

com.javarules.MemoryHandler.target = \
java.util.logging.FileHandler
com.javarules.MemoryHandler.size = 2500
com.javarules.MemoryHandler.push = SEVERE

(Note that \ is the line continuation character in the properties file format. I have
to use it in the book because of the page widths.) This logging configuration file
expropriates the “global” file handler ( java.util.logging.File-
Handler ) that would normally be used by the root logger and uses it as a tar-
get handler instead. Coding such as this offers the advantage of field service
engineers being able to change the logger configuration, but at a very significant
cost.
The big question is why this functionality was not built-in to the logging API.
The best (and only) RFE that I am aware of is 4635817, but I suspect there will
be others to follow. Bug Id 4533204 (marked “Closed, not a bug”) perhaps
failed as an RFE because it was too complicated, but is interesting because it
includes the following evaluation.
The configuration file is intended for simple configuration.

231. Ibid.

968 JAVA RULES


For more complex configurations it is possible to specify configuration
classes that can be run at configuration time for more elaborate set
up.232

There is no support for this view in the API docs. As stated in the API docs for
the logging package:
The Logging APIs offer both static and dynamic configuration control.
Static control enables field service staff to set up a particular configura-
tion and then re-launch the application with the new logging settings.
Dynamic control allows for updates to the logging configuration within a
currently running program.233

Something like the Loggers utility class or RFE 4635817 must, and I am sure,
eventually will be added to the java.util.logging package. Meanwhile, I
suspect most loggers and handlers will be configured dynamically because
doing so requires much less code.
Configuring a logger is actually quite simple. Most of the time this is accom-
plished in four steps (the order is not really significant):
• In the case of an internationalized application or applet, the name of the
resource bundle is passed to the factory method in the Logger class.
• The logger level is set either in the logging configuration file or by invoking
the setLevel(Level newLevel) method in the Logger class. If
additional filtering is required, the setFilter(Filter newFilter)
method is invoked. Filters will be discussed momentarily.
• Handlers are then created and added to the logger by invoking the
addHandler(Handler handler) method.
• The setUseParentHandlers(false) method is invoked so that log
records are not published by the handlers of parent loggers (including the
“global” handlers of the root logger).
Note the handlers are “added” while filters are “set” because there may be more
than one handler whereas there is at most one filter. Here is an example of con-
figuring a logger that uses a file handler.

232. Evaluation of Bug Id 4533204.


233. API docs for the java.util.logging package.

ASSERTIONS, EXCEPTIONS, AND LOGGING 969


private static Logger configureLogger() {
Logger logger = Logger.getLogger("com.javarules");
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
try {
Handler handler = new FileHandler("%h/javarules.log");
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
return logger;
}

The Filter interface consists of a single method, and is declared as fol-


lows.

package java.util.logging;
public interface Filter {
boolean isLoggable(LogRecord record);
}

Such interfaces are typically implemented as anonymous classes. For example,

logger.setFilter(new Filter() {
public boolean isLoggable(LogRecord record) {
if (record.getSourceMethodName().startsWith("get") &&
record.getLevel().intValue() <=
Level.FINE.intValue()) {
return false;
}
return true;
}
});

This filter in effect says, “Do not log trace information from accessor methods.”
Implementing a filter is that easy.
It is important to understand why levels and filters are used in both loggers
and handlers. This may seem redundant at first, but it gives the application pro-
grammer a much higher degree of control over the configuration. For example,
the default level for a ConsoleHandler is Level.INFO , whereas the

970 JAVA RULES


default level for a FileHandler is Level.ALL. If using both, the logger
must be set to Level.ALL. Even though all messages are logged, the console
will not be bombarded with tracing information written to file handler. On the
other hand, invoking logger.setLevel(Level.OFF) logging is dynami-
cally disabled without disturbing the configuration of the handlers.
Configuring a handler is conceptually more difficult than a logger. There is a
very deliberate design for configuring handlers that simply must be understood.
The first point to understand is that there are two distinct groups of handler
properties, those common to all handlers and those that are only used by spe-
cific handlers. There are four common handler properties listed in Table 6.6 and
each has both a get and a set method in the Handler class. Unlike some of

Table 6.6 The Four Common Handler Properties


Property Name Description

level The level is used to make the initial determination if a log record
should be published. The default is usually Level.ALL. The
StreamHandler and ConsoleHandler classes are an
exception. They default to Level.INFO.

filter A handler has at most one filter. I doubt very seriously if this
property would ever be included in a logging configuration file
because it would take more work to instantiate the class than it
would to create the filter (unless the filter were unusually complex).
The default is always no filter because filters must be
programmatically created.

formatter A handler has at most one formatter. Most of the time this is one of
the standard formatters SimpleFormatter (a plain text
formatter) or XMLformatter. The FileHandler and
SocketHandler classes default to an XML formatter. The
StreamHandler and ConsoleHandler classes default
to a plain text formatter. The MemoryHandler class does no
formatting and so ignores the formatter property.

encoding This is the character encoding scheme used when formatting the log
record. Always defaults to the default platform encoding. The
MemoryHandler class does no formatting and so ignores
encoding property.

ASSERTIONS, EXCEPTIONS, AND LOGGING 971


the handler properties used by specific handlers, all of the common handler
properties default if not specified. (In the case of a memory handler, the default
values are undocumented because memory handlers never format log records.)
Setting the formatter and encoding properties for a memory handler is therefore
pointless, but can be done because the code is inherited from the Handler
superclass. As noted in the setFormater(Formatter newFormatter)
method in the Handler class:
Some Handlers may not use Formatters, in which case the Formatter
will be remembered, but not used.234

The setEncoding(String encoding) method in Handler does not


include a similar comment, but probably should. This is actually questionable
object-oriented design, not unlike unsupported operations in the Collections
Framework.
The other group of handler properties listed in Table 6.7 have no get and
set methods in their respective handler classes with the one exception of the
getPushLevel() and setPushLevel(Level newLevel) method in
MemoryHandler. These properties are set in one of two ways. Either the no-
argument constructor is invoked and the values in the logging configuration file
are used or else all of the values must be passed to a constructor (file handlers
being a notable exception). You can see this design in the following list of con-
structors from all of the standard handler classes.

MemoryHandler()
MemoryHandler(Handler target, int size, Level pushLevel)

FileHandler()
FileHandler(String pattern)
FileHandler(String pattern, boolean append)
FileHandler(String pattern, int limit, int count)
FileHandler(String pattern, int limit, int count, boolean append)

SocketHandler()
SocketHandler(String host, int port)

234. API docs the setFormater(Formatter newFormatter) method in the


java.util.logging.Handler class.

972 JAVA RULES


Table 6.7 Specialized Handler Properties
Property Description

MemoryHandler.size This is the number of log records that a memory


buffer can hold. The default value is 1000 log
records. Allowing for two word object headers on a 32-
bit architecture, multiply this number by 112 to get the
approximate size of the buffer in bytes (112 KB by
default).

MemoryHandler.push This is the level that will automatically trigger the


publication of log records in a memory buffer. The
default is Level.SEVERE. Note that the property
name is push and not pushLevel (though this is
just a programmer convention).

MemoryHandler.target This is the name of the target handler class to which


the log records are published. There is no default.

FileHandler.limit The API docs describe this as an “approximate


maximum amount to write (in bytes) to any one file.”a
The default is zero, which means there is no limit. If the
file handler is a memory handler target, a good rule of
thumb is to multiply the memory handler size by 100 to
arrive at the file handler limit. If anything, the resulting
file size should be larger then is needed. There is no
way to accurately estimate the size of a log file, even if
you known the precise number of records written.

ASSERTIONS, EXCEPTIONS, AND LOGGING 973


Table 6.7 Specialized Handler Properties (Continued)
Property Description

FileHandler.count This property is essentially a command to use


generation numbers instead of overwriting the existing
log file. I say that because if count is greater than one
and %g is not included anywhere in the pattern string
then generation numbers are appended to the end of
the file name (after the .log file extension). Make a
mental note of this because I promise you it will happen
sooner or later. For example, if the count is two, the
pattern string is java.log, and java.log
already exists instead of overwriting that log file a new
one named java.log.1 is created (which is
something I seriously doubt anyone would expect to
happen). If the value is not specified, or if the specified
value is not a positive integer greater than zero the
count defaults to one.

FileHandler.pattern See Table 6.5 Escape Sequences in the


FileHandler Class on page 949 and surrounding
text for a discussion.

FileHandler.append If true is passed, an existing file is appended rather


than overwritten. Beware of Bug Id 4629315,
“Appending of XML Log files doesn't merge new
records” when appending to an XML log file. (The
header and footer are being rewritten.)

SocketHandler.host The host to which to connect. There is no default.

SocketHandler.port The TCP port. Again, there is no default.

a. API docs for the java.util.logging.FileHandler class.

The explanation for this design is that at least some (all in the case of a socket
handler) of these properties must be known before the handler object can be
created. Those same properties cannot be changed after the handler is created.
Hence no get and set methods. Overall, it is a very clean design.
When I first became aware of this design, I assumed that the three File-
Handler constructors between the no-argument constructor and the one that

974 JAVA RULES


accepted all of the optional constructor parameters in the above list would use the
values in the logging configuration file (or their specified defaults), which would be
consistent with the non-argument constructors. That is not the case, however. Only
the append property uses the value in the logger configuration file (or the speci-
fied default of false). The limit and count are set to 0 and 1, respectively,
which happen to be their default values, but these constructors will ignore the
limit and count in a logger configuration file. This is a reasonable inter-
pretation of the intent of a client programmer invoking one of these constructors,
but will inevitably confuse and confound some of them who expect (as I did) the
limit and count to be read from the logging configuration file (as is done in
every other case). This means that if either the FileHandler(String
pattern) or FileHandler(String pattern, boolean append)
constructors are used, the log file size is unlimited and generation numbers are not
used. Hence the following warning in these constructors:
There is no limit on the amount of data that may be written, so use this
with care.235

File handlers are discussed further in 6.11 Logging.

6.11.3 Logging Methods


Invoking one of the logging methods in the Logger class creates a Log-
Record that is immediately discarded, stored in a memory buffer, or published.
This section discusses both the logging methods and the log records they cre-
ate. There are 25 logging methods listed in Table 6.8. They break down as fol-
lows.
7. “bare bones” convenience methods
6. convenience methods for logging trace information
4. log methods
4. logp or log precise methods
4. logrb or log with resource bundle methods
25. total

235. API docs for the FileHandler(String pattern) and FileHandler(String


pattern, boolean append) constructors in the java.util.logging.FileHandler
class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 975


Table 6.8 Logging Methods
Method Short Description

void severe(String msg) These are convenience methods that invoke


void warning(String msg) log(Level level, String msg) using
void info(String msg) a level that corresponds to their method name.
void config(String msg)
void fine(String msg)
void finer(String msg)
void finest(String msg)

void These convenience methods are used to log


entering tracing information. What all three of them have in
String sourceClass
common is that both the level and message are
String sourceMethod
implicit. The level is Level.FINER , which is
void appropriate for tracing information. The
exiting messages always begin with "ENTRY",
String sourceClass "RETURN", or "THROW" (always in caps).
String sourceMethod
The first two methods log the fact that you are
void
entering or exiting a method or constructor. Upon
throwing
String sourceClass entering a no-argument method or constructor,
String sourceMethod the basic entering method shown to the left
Throwable thrown is used. There are two other entering
methods that use one of the following as the last
parameter.

Object param1
Object[] params
Upon exiting void methods or any constructor,
the basic exiting method shown to the left is
used. There is another exiting method that
uses the following as the last parameter:

Object result
Logging the fact that an exception is being thrown
is especially important because exceptions are
sometimes ignored or translated without properly
chaining the old exception to the new one.

976 JAVA RULES


Table 6.8 Logging Methods (Continued)
Method Short Description

void The basic log method is shown to the left. The


log other three log methods include one of the
Level level
following as the last parameter.
String msg
Object param1
Object[] params
Throwable thrown
Although not immediately apparent, these are also
convenience methods because the Logger
class makes a “best effort” to determine the
sourceClass and sourceMethod.

void These are the log precise or logp methods. The


logp basic method is shown to the left. The other three
Level level
String sourceClass
logp methods include one of the following as
String sourceMethod the last parameter:
String msg
Object param1
Object[] params
Throwable thrown

void These are the log with resource bundle or logrb


logrb methods. They should rarely if ever be invoked
Level level
because the logger already knows the resource
String sourceClass
String sourceMethod bundle name. The basic method is shown to the
String bundleName left. The other three logrb methods include one
String msg of the following as the last parameter:

Object param1
Object[] params
Throwable thrown

void All of the other methods in this table invoke this


log method after creating a LogRecord .
LogRecord record

ASSERTIONS, EXCEPTIONS, AND LOGGING 977


The “bare bones” convenience methods have names such as info and
severe that correspond to their log levels. These are by far the easiest logging
methods to use because they are only passed one argument (the log message).
There are six convenience methods for logging trace information: (3)
entering, (2) exiting, and (1) throwing method. These convenience
methods all have a level of FINER. The log methods differ from convenience
methods in that the log level must be passed (instead of being implied in the
method name). They differ from the logp methods in that the source class and
method names are inferred, which means that instead of passing them as argu-
ments the LogRocord class makes a “best effort” to determine them using a
stack trace. This subject is discussed at length below. In the sense that the log
methods are not passed the source class and method names, they too are con-
venience methods. The logp (or log precise) methods are so-named because
they are passed the log level, source class and method names, and message.
The logrb (or log with resource bundle) methods are rarely used.
If the logger was created using either the getLogger(String name,
String resourceBundleName) or getAnonymousLogger(String
resourceBundleName) factory methods, the logp methods use that
resource bundle name when creating the log record. About the only time using
the logrb methods is necessary is if for some reason you wanted to use a dif-
ferent resource bundle than the one passed to the logger factory method.
The log(LogRecord record) method is very special in that it is
invoked by all of the other logging methods after they create the log record. It
first determines if the log record should be published by comparing the level of
the log record against the level of the logger. Assuming that the set-
Filter(Filter filter) method has been invoked, it then invokes the
isLoggable(LogRecord record) method of the logger’s filter. If the log
record passes both of these tests then the publish(LogRecord record)
method for all of the handlers (including parent handlers if they are used) is
invoked as a means of passing the log record to handlers.
If any of the optional Object param1, Object[] params, Throw-
able thrown, or Object result arguments are passed to logging meth-

978 JAVA RULES


ods, their toString() methods are invoked and the result is append to the
log message. For example,

import java.util.logging.*;
import java.io.*;
class Test {
private static final String CLASS = "Test";
private static Logger logger = Logger.global;
static {
logger.setLevel(Level.ALL);
logger.setUseParentHandlers(false);
Handler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
logger.addHandler(consoleHandler);
}
public static void main(String[] args) {
new Test().copy(new File("java.log"), "Copy of java.log");
}
public File copy(File file, String newName) {
final String METHOD = "copy(File file, String newName)";
logger.entering(CLASS, METHOD,
new Object[]{file, newName});
File copy = new File(newName);
/* code to copy file */
Exception e = new IOException("something went wrong");
logger.throwing(CLASS, METHOD, e);
//just kidding
logger.exiting(CLASS, METHOD, file);
return copy;
}
}

Executing this program prints

Jul 1, 2002 5:49:12 PM Test copy(File file, String newName)


FINER: ENTRY java.log Copy of java.log
Jul 1, 2002 5:49:12 PM Test copy(File file, String newName)
FINER: THROW
java.io.IOException: something went wrong
at Test.copy(Test.java:21)
at Test.main(Test.java:14)
Jul 1, 2002 5:49:13 PM Test copy(File file, String newName)
FINER: RETURN java.log

ASSERTIONS, EXCEPTIONS, AND LOGGING 979


Notice that the throwing method invokes printStackTrace() rather
than just invoking toString() on the exception. This is a nice finesse.
The source class and method names passed to a logger are nothing more
or less than what I like to describe as code tags. A code tag is any device that
allows you to find where a diagnostic message originated in the source code.
There are three approaches to using code tags when logging:
• Some messages are so unique that there is no question as to where they
originated. In that case, logging the message using one of the convenience
methods is a reasonable choice.
• If the name of the class and method in which the logging method was
invoked are passed as arguments to a logging method, then these are very
good code tags. For reason discussed below, however, the “automatically
inferred”236 values are very unreliable. Bug Id 4465521 argues that the
compiler should provide the class and method name as arguments to log-
ging methods, much like it automatically provides a reference to the current
object (or to link variables for inner classes), which makes a lot of sense and
would eliminate a lot of “boiler plate” code from Java programs that use the
logging API. That thoughtfully worded RFE such as these are marked
“Closed, will not be fixed” in the Bug Parade seems crass to me.
• An alternative is to use the same error codes (or what the “Java Logging
Overview” document refers to as “unique message IDs”237) that are some-
times used when throwing exceptions. For example,
log(Level.SEVERE, "EM129: Log message with error code");

In mature systems, error codes such as these can be used in system docu-
mentation, making it possible for end-users to lookup more detailed informa-
tion about the cause of a particular problem.
If not passed as arguments, the LogRecord class makes a “best effort” to
determine the name of the class and method in which a log method was invoked,
but this effort is likely to fail in commercially available Java VM such as HotSpot.
As stated in the API docs:

236. API docs for the java.util.logging.LogRecord class.


237. Unascribed, “Java Logging Overview” document in the 1.4 release, §1.13, “Unique Message
IDs.”

980 JAVA RULES


For the methods that do not take an explicit source name and method
name, the Logging framework will make a “best effort” to determine
which class and method called into the logging method. However, it
is important to realize that this automatically inferred informa-
tion may only be approximate (or may even be quite wrong!). Vir-
tual machines are allowed to do extensive optimizations when JITing
and may entirely remove stack frames, making it impossible to reliably
locate the calling class and method.238 {Emphasis added}

This is, of course, a reference to method inlining, which is the single most impor-
tant optimization performed by state-of-the-art Java VM. Because all commer-
cially available Java VM optimize code by inlining methods, the frame that
invoked the logging method may have a different class and method name. (Pre-
sumably a different method name is what is meant by “approximate.” By exten-
sion, “quite wrong” would mean that both the class and method names are
wrong.) This is the same reason why stack traces sometimes say (Compiled
Code) instead of the source code file name and line number.
If a memory handler is used or if the log record has been serialized, the
source class and method names are sure to be wrong. See Bug Id 4515935,
“LogRecord lazy class/method name inference can be incorrect/unreliable”
for a discussion. It will be interesting to see how this is fixed. Moving the stack
trace search to the log(LogRecord record) method in Logger would be
contrary to existing documentation claims as to the cost of logging. This
dilemma makes a strong case for reconsidering Bug Id 4465521 (mentioned
above).
“Automatically inferring” the name of the class and method in which a log
method was invoked is a much simpler process than the API docs make it out to
be. I do the same thing in AWTExceptionHandler in order to determine the
name of the class and method in which a message was printed to standard out-
put or standard error. Here is the source code:

int index = 0; //"robust" default


StackTraceElement[] stack = new Throwable().getStackTrace();
Search:

238. API docs for the java.util.logging.Logger class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 981


for (int i=0; i<stack.length; i++) {
StackTraceElement frame = stack[i];
if (frame.getClassName().equals("java.io.PrintStream")) {
if (frame.getMethodName().equals("println")) {
index = ++i;
break Search;
} else if (frame.getMethodName().equals("print")) {
if (stack[i+1].getMethodName().equals("println")) {
index = i+2;
break Search;
} else {
index = ++i;
break Search;
}
}
}
}
StackTraceElement frame = stack[index];

logger.logp(level,
frame.getClassName(),
frame.getMethodName(),
message);

The Throwable class in instantiated in order to call the native fillIn-


StackTrace() method. A StackTraceElement is just another name for
a frame on the stack. Each frame has a class and method name, so all you have
to do is search backwards (starting from index zero) through the stack until a
frame with the right class and method name is found. In my case I am searching
for a frame with the class name java.io.PrintStream and a method
name of println. The frame below that is a “best effort” to determine the
name of the class and method in which System.out.println or
System.err.println was invoked. (By redirecting standard output and
standard error to different streams I can also determine which was used.) The
code in LogRecord is actually simpler because it merely searches backwards
through the stack until a frame that does not have a class name of
java.util.logging.Logger is found, whereas I have to take into consid-
eration that the overloaded println methods in PrintStream invoke the
corresponding print method. For example, here is the top of a stack trace

982 JAVA RULES


from within one of the overriding flush() methods in AWTException-
Handler when System.out.println is invoked:

at AWTExceptionHandler$StandardOutputStream.flush(AWTExceptionHandler.java:230)
at java.io.PrintStream.write(PrintStream.java:260)
at sun.nio.cs.StreamEncoder$CharsetSE.writeBytes(StreamEncoder.java:334)
at sun.nio.cs.StreamEncoder$CharsetSE.implFlushBuffer(StreamEncoder.java:402)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:113)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:169)
at java.io.PrintStream.write(PrintStream.java:305)
at java.io.PrintStream.print(PrintStream.java:448)
at java.io.PrintStream.println(PrintStream.java:585)

As you can see besides the print method there are seven other methods on
top of println by the time flush() is invoked. About the only trick to doing
this is to remember that the class name in a StackTraceElement is always
fully qualified.
A LogRecord looks like this in memory:

public class LogRecord {


private Level level;
private long sequenceNumber;
private String sourceClassName;
private String sourceMethodName;
private String message;
private int threadID;
private long millis;
private Throwable thrown;
private String loggerName;
private String resourceBundleName;
private transient boolean needToInferCaller;
private transient Object[] parameters;
private transient ResourceBundle resourceBundle;
}

There are thirteen fields, three of which are transient (and therefore not seri-
alized). There are a total of nine reference types plus 24 bytes of primitive data.
That is 96 bytes of data on a 32-bit architecture.
There are get and set methods for all of these fields (except needTo-
InferCaller) as well as the public LogRecord(Level level,
String msg) constructor, making it possible for application programmers to

ASSERTIONS, EXCEPTIONS, AND LOGGING 983


create their own log records and then invoke log(LogRecord record) to
publish the record instead of invoking one of the logging methods. Table 6.9
lists all of the fields in a log record.
If creating your own log records, the API docs include the following warning:
When a LogRecord is passed into the logging framework it logically
belongs to the framework and should no longer be used or updated by
the client application.239

Basically what this means is that you should not keep a reference to the
LogRecord after invoking log(LogRecord record).

6.11.4 The Cleaner Thread (A Shutdown Hook in LogManager)


Should the LogManager class be registering a shutdown hook that invokes
reset() ? The API doc for that method reads as follows.
Reset the logging configuration.

For all named loggers, the reset operation removes and closes all Han-
dlers and (except for the root logger) sets the level to null. The root log-
ger's level is set to Level.INFO.240

The effect of invoking the reset() method is that the logging API can-
not be used during shutdown. Bug Id 4682478 addresses this question, but
misses the mark because it suggests an ordering (or reordering) of shutdown
hooks. The evaluation correctly notes that it “is dangerous to rely on ordering of
shutdown operations.”241 Shutdown hooks are a bunch of unstarted threads that a
JVM starts and runs concurrently. They are not and never will be ordered. See
java.sun.com/j2se/1.3/docs/guide/lang/hook-design.html for a dis-
cussion. The question, however, is not the fact that shutdown hooks are unordered.
The question is: Should the log manager be invoking the reset() method?
In other words, should LogManager be registering a shutdown hook at all?
If the answer is yes, the only plausible explanation is the push() method in
a memory handler, which could potentially publish thousands of log records dur-

239. API docs for the java.util.logging.LogRecord class.


240. API docs for the reset() in the java.util.logging.LogManager class.
241. Evaluation of Bug Id 4682478

984 JAVA RULES


Table 6.9 The Fields in a LogRecord
Fielda Description

Level Both of these fields are passed as arguments to the


level LogRecord constructor. Only level throws a
String
NullPointerException.
message

long This number is assigned when the LogRecord is


sequenceNumber created. The primary significance of the sequence-
Number is that it determines the order in which
MemoryHandler publishes records. Other
handlers publish log records in the order they are
created. Assuming the neither setMillis(long
millis) or setSequenceNumber(long
seq) are not invoked by client programmers, log
records are always published in chronological order.

String If not set, the LogRecord class makes a “best


sourceClassName effort” to determine the source class and method name
when either the getSourceClassName() or
String
sourceMethodName getSourceMethodName() methods are
invoked (typically when the log record is formatted), but
doing so is very problematic for a number of reasons.

int Both of these fields are set in the LogRecord


threadID constructor. Nevertheless, there are public
mutator methods for both of them. The threadID
long
millis field is an arbitrary number starting at 10 (a
ThreadLocal variable) that identifies the thread in
which a logging method was invoked. This field is not
used at all by the java.util.logging
package. The millis field is the date and time when
the LogRecord was created. It is set by invoking
System.currentTimeMillis().

ASSERTIONS, EXCEPTIONS, AND LOGGING 985


Table 6.9 The Fields in a LogRecord (Continued)
Field a Description

Throwable Unlike the parameters array, thrown is not a


thrown transient field because the baseclass
Throwable is serializable. The other reason for
storing Throwable objects in a separate field is
that they are not converted to strings and then
appended to the end of the log message as are objects
in the parameters array discussed in the next row.
Instead, a Throwable object stored in this field
triggers the printing of a stack trace.

String This is the same name used to create the logger or


loggerName null for an anonymous logger.

ResourceBundle ResourceBundle is not serializable, so the


resourceBundle resourceBundle field is transient. This
would be an implementation detail were it not for the
String
resourceBundle- fact that it explains why there is a separate
Name resourceBundleName field. It is used in the
readObject(ObjectInputStream in)
method to “regenerate” the resource bundle. Be sure
to set both if there is any chance that the log record will
be serialized. Otherwise, the Formatter classes
only use resourceBundle.

Object[] Many of the methods in Table 6.8 Logging Methods are


parameters overloaded for passing method and constructor
parameters as either an Object or Object[]
(depending of course on whether or not there is more
than one parameter). Regardless of which one of these
logging message parameters types is used, the data is
stored in the LogRecord as an Object[]
named parameters.

a. All of these are private instance variables. Class variables are not included in this table. Note that
parameters[] and resourceBundle are transient, but for reasons explained in the table are written to
the serialized form as strings.

986 JAVA RULES


ing shutdown. There is no documentation to support this theory (which in and of
itself is a problem), but it is at least possible that the Cleaner thread (which is
the name of the shutdown hook registered in LogManager) is designed pre-
cisely so that application programmers would not use the logging API in a shut-
down hook. Logging one or two messages in a shutdown hook would not be a
problem, but invoking push() for a memory handler that has thousands of log
records that must be formatted (which may include localization) could be a prob-
lem. As noted in the addShutdownHook(Thread hook) method:
Shutdown hooks should… finish their work quickly. When a program
invokes exit the expectation is that the virtual machine will promptly
shut down and exit. When the virtual machine is terminated due to user
logoff or system shutdown the underlying operating system may only
allow a fixed amount of time in which to shut down and exit. It is there-
fore inadvisable to attempt any user interaction or to perform a long-
running computation in a shutdown hook.242

I think this warning should suffice, however, and that the logging API should be
available to application programmers during shutdown. At a bare minimum the
LogManager class should document the rationale for making the logging API
unavailable during shutdown.
There are numerous examples of why the logging API should be available
during shutdown. Suppose, for example, that you have a performance critical
application for which the log file must be saved after each run. The push level is
set to Level.OFF so that no records are published while the application is run-
ning, but there is a requirement to explicitly invoke the push() method during
shutdown. It would be natural to do this in a shutdown hook inside of the
configureLogger() method. For example,

private static Logger configureLogger() {


Logger logger = Logger.getLogger("com.javarules");
logger.setLevel(Level.ALL);
FileHandler target = null;
try {

242. API docs for the addShutdownHook(Thread hook) method in the


java.lang.Runtime class.

ASSERTIONS, EXCEPTIONS, AND LOGGING 987


target = new FileHandler("%h/javarules.log", 200000, 1);
target.setFormatter(new SimpleFormatter());
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
memoryHandler = new MemoryHandler(target,
2000,
Level.OFF);
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
memoryHandler.push();
}
});
logger.addHandler(memoryHandler);
logger.setUseParentHandlers(false);
return logger;
}

As noted above, however, shutdown hooks are a bunch of unstarted threads that
the JVM runs concurrently. There is no guarantee this shutdown hook will run
before the log manager closes the handler. (In my experience, the log manager
shutdown hook always runs first.) Closing a memory handler does two things.
The target handler is closed and the memory handler level is set to OFF, which
means that when the shutdown hook code above runs (assuming it runs after the
shutdown hook registered by the log manager), nothing is published and the
push() method completes normally. Thus shutdown hooks cannot be used
to invoke the push() method. The workaround is to invoke the push()
method before shutdown. In single-threaded applications, the push() method
can be invoked at the bottom of the main method. GUI applications should
invoke it in AWTExceptionHandler (or whatever you call your top-level
exception handler) as well as in the windowClosing(WindowEvent e)
method. See also Bug Id 4744270, “The Cleaner shutdown hook is application
code” (which is a bug that I submitted).

988 JAVA RULES


Vocabulary List
Click on the page number for a def- arbitrarily named logger...............956
inition of any of the terms in this arbitrary blocks ...........................567
vocabulary list. arbitrary-length sequence of
unsigned bytes............................504
argument check ..........................147
array type assignment
compatibility ...............................617
Symbols assert false .........................680
% radix ................................... 499 assignable ..................................615
& 0xf ....................................... 499 assignment compatible ................614
(Compiled Code) ................... 924 assignment conversion context ....623
(Native Method) ................... 924 associative property ....................428
(Unknown Source) ................. 924 asynchronous exceptions .............760
automatic conversions .................599
AWT component dump.................932

Numerics
0xF ........................................... 463
0xf ........................................... 463 B
0xFF ......................................... 464 backtrace ...................................922
0xff ......................................... 464 backwards recovery ....................860
baseclass ...................................340
basic rule of protected access .303
binary branch ..............................532
binary branching order.................467
A binary numeric promotion ............413
abnormal termination .................. 561 binary operators..........................435
abrupt completion....................... 560 binary values...............................477
access ....................................... 286 bit ..............................................477
access mode.............................. 831 bit flags ......................................477
accessible.................................. 291 bit flipper (operator).....................485
activation frame.......................... 686 bit masks....................................463
actual argument.......................... 144 bit set.........................................479
addends..................................... 444 bitset .....................................477
ad-hoc polymorphism .................. 587 bitwise operators ........................454
alpha value ................................. 521 bitwise programmer ....................475
ancestor (tree data structures)..... 953 blank final ...................................102
AND ........................................... 454 block ..........................................566
anonymous loggers..................... 954 block-private ...............................300
applicable method....................... 634 boolean equality operators ...........445
application namespace................ 251

VOCABULARY LIST 989


boolean logical operators ............ 450 code array ................................ 695
branching statements .................. 530 code tag .................................... 980
break target ............................... 562 combined bit masks.................... 463
Bug Ids.......................................XXVI common exception classes ......... 724
bug releases............................... XXIII compilation unit scope ................ 268
bugs .......................................... 706 compiler-enforced method
built-in method overloading .......... 635 contract..................................... 215
byte ........................................... 501 compile-time constant expression .. 83
byte buffer.................................. 841 compile-time declaration ............. 191
byte channel ............................... 504 complete normally ...................... 560
byte mask .................................. 464 complex assertion ...................... 665
byte shifting................................ 512 complex exceptions .................... 724
byte stream ................................ 504 complex primary expressions ...... 398
byte train.................................... 504 complex typesafe enum .............. 118
compound statement .................. 567
computational types.................... 409
conceptual data types................. 475
conceptual frameworks ..................XV
C conditional operator.................... 467
C stack ...................................... 687 conditional-and ........................... 451
call chain .................................... 687 conditional-or ............................. 451
call stack.................................... 685 consistent state.......................... 859
cannot resolve symbol consistent state restoration......... 860
error message............................ 194 console events ........................... 934
capping a class hierarchy ............ 387 constant interface antipattern ...... 130
case constant............................ 539 constant variable .......................... 94
case label.................................. 539 constructor mathematics .............. 45
case statement.......................... 538 containment-hierarchy-private ...... 299
catch block .............................. 785 continue target ....................... 561
catchall exception handlers .......... 799 control-flow invariants ................. 679
cause (of an exception)................ 873 control-flow statements ............... 529
character buffer .......................... 841 convenience constants................ 108
checkcast ............................... 638 conveyor belt (operator) .............. 460
checked exceptions .................... 699 covariant result type ................... 224
Chicken Little “the sky is falling” Ctrl+\ ..................................... 933
problem ..................................... 754 Ctrl+Break............................. 933
children (tree data structures) ...... 953 current class .............................. 688
circular initialization ....................... 56 current frame ............................. 688
class file disassembler ................ XXV current method .......................... 688
class invariant............................. 675 current offset ............................. 831
class type privileges.................... 289 cyclic initialization ......................... 56
class-private ........................... 299
clearing a bit............................... 477

990 JAVA RULES


D every method errors ....................712
exception class ...........................698
DBC........................................... 671
exception handler ........................791
dead code.................................. 184
exception handling.......................859
deadlock detection utility ............. 932
exception handling strategies .......862
debugging exceptions ................. 707
exception specification ................165
declaration statement.................. 527
exception tables ..........................693
declaring class ........................... 650
exception translation ...................867
default access ............................ 290
exception-handler parameter ........791
default exception handler............. 883
exception-handling conversion
default label................................ 542
context .......................................792
default logging configuration file... 960
exclusive-OR ...............................454
default logging level .................... 956
execution stacks .........................685
defensive copies......................... 161
explicit precedence .....................420
definite assignment ..................... 181
explicitly thrown (exceptions) ........723
deliberate system-induced
exported.....................................325
argument check.......................... 154
expression statements.................403
Design by Contract ..................... 671
determinate loop......................... 550
difference................................... 444
digit mask .................................. 499
discrete set of integers ............... 113 F
dividend ..................................... 444 factory methods ............................50
divisor........................................ 444 failure information........................763
double-assignment ...................... 103 fall through .................................141
duplicate case label .................... 541 fall through comment...................545
dynamic enclosure ...................... 692 fd leak ........................................829
dynamically ................................ 463 fetch and save (in compund
dynamically assigned initial values .. 43 assignment operations)................435
dynamically combined bit masks .. 463 field access expressions ..............194
field modifiers ...............................36
file descriptor..............................829
file descriptor table .....................830
E finally block...........................785
five general forms .......................193
embedded assignments .............. 471
flags .......................................477
empty method implementation ..... 132
for (;;) .................................531
empty statement......................... 528
for statement header .................546
enclosing scope.......................... 265
forbidden conversions .................595
engineer version numbers ............ XXII
ForInit.........................................546
enumerated constants................. 106
formal parameter ........................144
enumerated type......................... 106
formal parameter list ...................144
error chute ................................. 802
ForUpdate...................................546
error codes ................................ 980
forward reference..........................56
error recovery ............................ 860

VOCABULARY LIST 991


forwards recovery ....................... 860 implementation inheritance .......... 345
four general rules (for widening implementation-only classes ........ 318
reference conversions) ................ 615 implicit narrowing conversions ..... 628
four levels of access control ........ 291 in scope..................................... 264
frame ......................................... 686 inclusive-OR................................ 454
full thread dump .......................... 932 incompatible ................................ 93
fully evaluated ............................. 401 inconstant constants..................... 95
fully overloaded........................... 228 indeterminate loops .................... 554
fully qualified............................... 253 infinite loops............................... 531
functionality release..................... XXIII infix binary operators .................. 435
initial logging configuration .......... 959
inlined constants........................... 90
inner-class-hierarchy-private ......... 299
integer bitwise operators ............ 450
G interface constant modifiers .......... 36
general exception class ............... 725 interface inheritance ................... 345
general exception handler............ 792 interface type privileges .............. 289
generalization ............................. 344 intermediate results .................... 179
generation number ...................... 949 internal invariant ......................... 675
global handlers ........................... 943 interpreted mode........................ 927
global logger .............................. 950 invariant..................................... 674
invocation modes ....................... 236
invoker ...................................... 687
IP addresses .............................. 520
H iteration statements .................... 530
hall of mirrors effect...................... 71
hard limit .................................... 836
hiding ......................................... 332
high-level exceptions ................... 725 J
hooks......................................... 382 Java code .................................. 739
Java Development Kit...................XXV
Java virtual machine stacks ......... 685
javap ........................................XXV
I JDK ............................................XXV
JNU ........................................... 740
identity conversion ...................... 600
JVM core dump .......................... 932
immediately contains ................... 529
JVM thread dump........................ 932
Immutable Inners (design pattern). 159
immutable object ........................ 155
immutable subinterface ............... 159
implementation (of a class or
interface type)............................. 581 K
implementation (of a method) ....... 132 kernel limit ................................. 836
implementation dependencies ...... 375

992 JAVA RULES


L method header............................131
method invocation chain (stacks) ..687
labeled block .............................. 568
method invocation chaining ..........204
labeled statement ....................... 555
method invocation conversion
labels......................................... 554
context .......................................632
last ditch recovery efforts............ 893
method invocation expression ......194
lexical constructs........................ 265
method modifiers ........................132
lexical scoping............................ 259
method return conversion context 637
link variable ................................ 314
microbenchmark tests .................125
local variable array...................... 175
Microsoft is nutty (a derisive
log level ..................................... 937
mnemonic)..................................415
log message .............................. 937
minimal constructor.......................46
log precise (or logp) .................. 975
minuend .....................................444
log records ................................ 937
mixed mode................................927
log report................................... 937
modes of operation .....................927
log with resource bundle (or
modulo operator .........................447
logrb) ...................................... 975 monitor cache dump....................932
logger namespace ...................... 953
most specific applicable method...645
logging configuration class .......... 959
multi-level break ..........................565
logging configuration file ............. 960
multiplicand ................................444
logging control properties ........... 958
multiplier.....................................444
logging methods......................... 975
multi-way branch .........................538
logic traps.................................. 679
multi-way if-else ...........................533
loop labels.................................. 555
loop unrolling.............................. 518
loop-continuation point................. 561
loops ......................................... 530
loss of information ...................... 598 N
lost exceptions ........................... 789 named loggers............................954
low-level exceptions .................... 726 named namespaces ....................251
lvalues ....................................... 394 namespace .................................249
narrowing conversion...................597
native method stack.................687
non-associative ...........................429
M nondestructive ............................430
nonfunctional groups ...................725
maintenance releases ................. XXIII
non-local jump .............................768
major release ............................. XXIII
non-local transfer of control..........768
matching case label .................... 542
non-occurring errors ....................713
matching catch clause .............. 696
non-public ...............................290
matching methods ...................... 634
normal completion.......................141
matching types ........................... 445
normal mode ..............................560
method body .............................. 131
NPE............................................173
method descriptor ...................... 355
numeric promotion ......................410
method dispatch tables ............... 352

VOCABULARY LIST 993


numerical equality operators ........ 445 pixels......................................... 521
nybble ........................................ 492 place value shifting ..................... 499
nybble mask ............................... 463 point-of-origin ............................. 431
nybble shifting............................. 512 polymorphic ............................... 234
polymorphism ............................ 587
postcondition ............................. 678
postfix operators ........................ 435
preconditions ............................. 149
O pre-existing binaries ...................... 93
object packing ............................ 479 prefix operators.......................... 435
object-oriented enumerated type .. 111 primary expression ..................... 397
obscured.................................... 281 primary expression general form . 197
observable ................................. 285 primitive constants ....................... 93
octet .......................................... 501 process ..................................... 830
official release names...................XXII product...................................... 444
opcode 192 ............................. 638 program counter ........................ 695
open files leak............................. 829 program counter register ............ 695
open files table ........................... 831 propagated ................................ 723
operand expressions ................... 405 protected implementation ....... 326
operands.................................... 405 pseudo-constructors ..................... 63
operating system signal............... 934 public interface ....................... 325
operations .................................. 405 pure substitution......................... 589
operator expression .................... 405 push level .................................. 946
operator order of precedence ...... 415
optional constructor parameter ...... 44
order of evaluation ...................... 399
out of scope ............................... 264
out-of-band signal ........................ 143 Q
overloaded method matching ....... 644 qualified access.......................... 287
overloaded methods.................... 225 qualified name ............................ 253
overridable methods.................... 376 qualifying type ............................ 188
overriding ................................... 233 quotient ..................................... 444

P R
package-private .......................... 290 read-only access ........................ 160
parameter specifier ..................... 144 records...................................... 774
parametric polymorphism ............ 587 recovery .................................... 860
parents ...................................... 953 recurring end-user error .............. 715
partially qualified ......................... 253 reference constructor ................... 48
pattern ....................................... 948 reference equality operators........ 445
pc register ................................. 695 re-inheritance ............................. 367

994 JAVA RULES


related classes ........................... 342 Software Development Kit ............ XXV
required constructor parameter ..... 44 source code transformations........313
resource leak ............................. 824 squelching an exception...............874
result type (of methods)............... 139 sRGB..........................................521
result type (of operator stack frame ................................686
expressions)............................... 407 stack trace .................................922
resumption ................................. 879 standard default values ..................38
retry .......................................... 879 standard exceptions ....................699
return value ................................ 139 statement ...................................526
RGB colors ................................. 521 statement group..........................538
rollback...................................... 860 static member general form.....196
rollforward.................................. 860 static type checking ....................583
root logger................................. 953 statically compiled initial values ......43
root node ................................... 953 storage types..............................410
run time ..................................... 704 strongly typed language...............583
run-time...................................... 704 subsystem specific
runtime ...................................... 704 unspecified checked exception .....735
run-time check ............................ 585 subtrahend .................................444
runtime exception ....................... 705 subtypes.....................................342
runtime exception authentication sum............................................444
test............................................ 709 superclass implementation hooks .382
superstitious programming ..........806
supertypes..................................342
switch block.............................538
switch block statement group....538
S switch label..............................538
safe type conversion ................... 599 symbolic references ....................195
safepoints .................................. 759 system namespace .....................250
scope ........................................ 248 system resources........................823
scope hole ................................. 266 system temporary directory .........949
SDK ........................................... XXV system thread group ...................896
setting (a bit) .............................. 477 system-induced argument check...152
shift distance.............................. 457
shutdown hooks.......................... 892
side effect.................................. 402
sign extension ............................ 605
simple assertion ......................... 665 T
simple assignment conversion target .........................................947
context ...................................... 627 target handler .............................947
simple exception......................... 724 taxonomy....................................330
simple name............................... 253 termination .................................879
simple typesafe enum ................. 115 ternary operators ........................435
single use mutator ...................... 749 throws clause conflict................222
soft limit..................................... 836 toggle ........................................465

VOCABULARY LIST 995


top-level exception handlers ......... 884 unspecified checked exceptions .. 735
top-level expression..................... 403 unspecified error ........................ 736
tree data structures .................... 953 unspecified runtime exception ..... 736
truth tables ................................. 454 unwinding................................... 690
try block................................... 785 user’s home directory ................. 949
type ........................................... 578
type checking ............................. 583
type comparison operator............ 473
type conversion boundaries ......... 595
type privileges ............................ 289
V
typesafe enum pattern................. 111 value set conversion ................... 656
variable declarator........................ 37
vector of bits.............................. 479
visible ........................................ 278

U
umbrella exception ...................... 731
unary numeric promotion ............. 412
unary operators .......................... 435
W
uncaught exception ..................... 883 while (true) ........................ 531
unchecked exceptions ................. 699 widening conversions.................. 597
unique number ............................ 950 window of vulnerability ................ 162
universal data type ...................... 501
unnamed bit masks ..................... 482
unnamed namespace .................. 251
unrecoverable checked X
exceptions.................................. 725
XOR........................................... 455
unsafe type conversion................ 599
unsigned byte ............................. 501

996 JAVA RULES


Index
This index is being phased in over C
a number of different versions. It
should be completed some time circular initialization...................56
class initialization methods ..35, 79
mid to late September, 2003.
class variables
initialization.............................79
For term definitions (especially for memory allocation...................38
terms “of my own making”), see constructors ......................... 42–48
the Vocabulary List on the preced- alternatives to the use of ... 50–55
ing pages. I do not index terms constructor parameters ..... 44–48
that are specific to my work. They reference constructors ...... 48–50
can only be found in the Vocabulary See also anonymous classes
List. cyclic initialization......................56

D
Symbols declarators ........................... 36–37
<clinit> .................................. 35
See also class initialization
methods
<init> ...................................... 35
See also instance initialization E
methods ExceptionInInitializerErr
or .........................................80

A
anonymous classes F
using instance initialization blocks factory methods ................... 50–53
as constructors for ............. 41 field access expressions
arrays the five general forms of..........35
array component initialization... 39 fields
declaration syntax ............. 35–37
field modifiers .........................36
initialization.............................38
initialization anomalies .............55
variable initializers for........68, 70

INDEX 997
five general forms (of field access methods
and method invocation expres- See also factory methods
sions) ..................................... 35 modifiers.................................... 35
forward references .............. 56–61 illegal combinations ................ 37
interface constant modifiers .... 36
order of ................................. 36
See also access modifiers,
static, final,
I transient, volatile,
synchronized, native,
initialization blocks............... 39–42
strictfp
must complete normally .......... 41
used as constructors in anony-
mous classes ..................... 41
inlined constants
always appear to have been initial-
ized ............................. 65–68 N
instance initialization blocks native ...................................... 35
See also initialization blocks
instance initialization methods .. 35
instance initializers
See instance initialization blocks
instance intialization O
See object initialization
object initialization
instance variables
invoking overridden methods dur-
memory allocation .................. 38
ing object initialization ....61–65
interfaces
StackOverflowError 70–71
interface constant modifiers .... 36
throwing checked exceptions dur-
ing................................. ??–81

L
link variables .............................. 63 P
local variables
parameters
declaration syntax............. 35–37
See also constructor parameters

M R
method invocation expressions
reference constructors
the five general forms of ......... 35
See constructors

998 JAVA RULES


S T
StackOverflowError ...... 70–71 threadsafe ..............................37
standard default values . 38–39, 56 transient ..........................35, 37
static ...................................... 35
static initialization blocks .......... 79
See also initialization blocks
static initializers
See static initialization blocks V
strictfp .................................. 35
variable declarators
synchronized ......................... 35
See declarators
variable initializers
See fields and local variables
volatile ............................35, 37

INDEX 999
1000 JAVA RULES

You might also like