Java is an object-oriented (OO) language in the tradition of Simula-67, SmallTalk, and C++
. It borrows syntax from C++
and ideas from SmallTalk. The Java API has been designed and built on the OO model. Design Patterns (see the book of the same name), such as Factory and Delegate, are used throughout; an understanding of these patterns will help you better understand the use of the API and improve the design of your own classes.
There are any number of short bits of advice that I could give. A few recurring themes arise when learning the basics of Java, and can usefully be reviewed when learning more Java.
I can’t say this often enough. A lot of the things you need to do have already been done
by the good folks who develop the standard Java library (and third-party libraries).
And this grows with every release. Learning the API well is a good grounds for avoiding that deadly “reinventing the flat tire” syndrome—coming up with a second-rate equivalent of a
first-rate product that was available to you the whole time. In fact, part of this book’s
mission is to prevent you from reinventing what’s already there. One example of this is
the Collections API in java.util
, discussed in Chapter 7. The Collections
API has a high degree of generality and regularity, so there is often no need
to invent your own data structuring code.
There is one exception to the “use the API” rule: the clone()
method in java.lang.Object
should generally not
be used. If you need to copy an object, just write a copy method, or a “copy constructor.”
Joshua Bloch’s arguments against the clone()
method in the book Effective Java (Addison-Wesley) are persuasive,
and should be read by any dedicated Java programmer. While you’re at it, read that whole book.
Another exception is the finalize()
method in java.lang.Object()
. Don’t use it. It
has been deprecated since Java 9, because it
isn’t guaranteed to be invoked, but because it might get invoked, it will cause your dead
objects not to be garbage collected, resulting in a memory leak. If you need some kind of
cleanup, you must take responsibility for defining a method and invoking it before you let
any object of that class go out of reference. You might call such a method cleanUp()
.
For application-level cleanup, see https://darwinsys.com/java/shutdownhook.html.
There is a trade-off between generality (and the resulting reusability), which is emphasized here, and the convenience of application specificity. If you’re writing one small part of a very large application designed according to OO design techniques, you’ll have in mind a specific set of use cases. On the other hand, if you’re writing “toolkit-style” code, you should write classes with few assumptions about how they’ll be used. Making code easy to use from a variety of programs is the route to writing reusable code.
You’ve no doubt looked at the Java online documentation in a browser, in part because I just told you to learn the API well. Do you think Sun/Oracle hired millions of tech writers to produce all that documentation? No. That documentation exists because the developers of the API took the time to write javadoc comments, those funny /**
comments you’ve seen in code. So, one more bit of advice: use javadoc. The standard JDK provides a good, standard mechanism for API documentation. And use it as you write the code—don’t think you’ll come back and write it in later. That kind of tomorrow never comes.
See Recipe 15.2 for details on using javadoc.
Use subclassing. But don’t overuse subclassing. It is one of the best ways not only for avoiding code duplication, but for developing software that works. See any number of good books on the topic of object-oriented design and programming for more details.
There are several alternatives. One alternative to subclassing is delegation. Think about “is-a” versus “has-a.”
For example, instead of subclassing NameAndAddress
to make BusinessPartner
and Customer
, make
BusinessPartner
and Customer
have instances of NameAndAddress
. That is a clearer structure;
having BusinessPartner
be a NameAndAddress
just because the partner has a name and address would not make sense.
And delegation also makes it easier for a Customer
to have both a billing address and a shipping
address. Another alternative is aspect-oriented programming (AOP), which allows you to
“bolt on” extra functionality from the outside of your classes. AOP is provided by the
Java EE using EJB Interception, and by the Spring Framework AOP mechanism.
In the Preface, I mentioned Design Patterns (Addison-Wesley) as one of the Very Important Books on object-oriented programming. Often called the “Gang of Four” (GoF) book for its four authors, it provides a powerful catalog of things that programmers often reinvent. Some people find the GoF book to be somewhat academic in tone; a less-formal presentation on patterns is the O’Reilly book +Head First Design Patterns_; this covers the same two dozen patterns as the GoF book. A Design Pattern provides a statement of a problem and its solution(s), rather like the present book, but generally at a higher level of abstraction. It is as important for giving a standard vocabulary of design as it is for its clear explanations of how the basic patterns work and how they can be implemented.
Table 8-1 shows some example uses of Design Patterns in the standard API.
Pattern name | Meaning | Examples in Java API |
---|---|---|
Command |
Encapsulate requests, allowing queues of requests, undoable operations, etc. |
|
Decorator |
One class “decorates” another |
Swing Borders |
Factory Method |
One class makes up instances for you, controlled by subclasses |
|
Iterator |
Loop over all elements in a collection, visiting each exactly once |
|
Model-View-Controller |
Model represents data; View is what the user sees; Controller responds to user requests |
|
Proxy |
One object stands in for another |
RMI, AOP, Dynamic Proxy |
Singleton |
Only one instance may exist |
|
I have written articles on the State, Proxy, Command, Decorator, and Visitor patterns for Oracle Java Magazine.
You want your objects to have a useful default format, and to behave themselves when placed in Collections classes.
There are four overridable methods inherited from java.lang.Object
;
of these, toString()
provides default formatting, while equals()
and hashCode()
provide equality testing and efficient usage in Map implementations.
The fourth, clone()
, is not recommended for general use.
Whenever you pass an object to System.out.println()
or any equivalent method, or involve it in string concatenation, Java automatically calls its toString()
method. Java “knows” that every object has a toString()
method because java.lang.Object
has one and all classes are ultimately subclasses of Object
. The default implementation, in java.lang.Object
, is neither pretty nor interesting: it just prints the class name, an @ sign, and the object’s hashCode()
value. For example, if you run this code:
public
class
ToStringWithout
{
int
x
,
y
;
/** Simple constructor */
public
ToStringWithout
(
int
anX
,
int
aY
)
{
x
=
anX
;
y
=
aY
;
}
/** Main just creates and prints an object */
public
static
void
main
(
String
[]
args
)
{
System
.
out
.
println
(
new
ToStringWithout
(
42
,
86
));
}
}
you might see this uninformative output:
ToStringWithout@990c747b
To make it print better, you should provide an implementation of toString()
that prints the class name and some of the important states in all but the most trivial classes. This gives you formatting control in println()
, in debuggers, and anywhere your objects get referred to in a String
context. Here is the previous program rewritten with a toString()
method:
public
class
ToStringWith
{
int
x
,
y
;
/** Simple constructor */
public
ToStringWith
(
int
anX
,
int
aY
)
{
x
=
anX
;
y
=
aY
;
}
@Override
public
String
toString
()
{
return
"ToStringWith["
+
x
+
","
+
y
+
"]"
;
}
/** Main just creates and prints an object */
public
static
void
main
(
String
[]
args
)
{
System
.
out
.
println
(
new
ToStringWith
(
42
,
86
));
}
}
This version produces the more useful output:
ToStringWith[42,86]
This example uses String
concatenation, but you may also want
to use String.format()
or StringBuilder
; see Chapter 3.
To ensure your classes work correctly when any client code calls equals()
or
when these objects are stored in Map
or other Collection
classes,
outfit your class with an equals()
and hashCode()
method.
How do you determine equality? For arithmetic or Boolean operands, the answer is simple: you test with the equals operator (==
). For object references, though, Java provides both ==
and the equals()
method inherited from java.lang.Object
. The equals
operator can be confusing because it simply compares two object references to see if they refer to the same object. This is not the same as comparing the values of the objects themselves.
The inherited equals()
method is also not as useful as you might imagine. Some people seem to start their lives as Java developers thinking that the default equals()
magically does some kind of detailed, field-by-field or even binary comparison of objects. But it does not compare fields! It just does the simplest possible thing: it returns the value of an ==
comparison on the two objects involved! So, for any value classes you write, you probably have to write an equals
method.1 Note that both the equals
and hashCode
methods are used by Map
s or hashes (such as HashMap
; see Recipe 7.9). So if you think somebody using your class might want to create instances and put them into a Map
, or even compare your objects, you owe it to them (and to yourself!) to implement both equals()
and hashCode()
, and to implement them properly.
Most IDEs know how to generate a correct equals() and hashCode() method, but it’s worth your while
to understand what these are doing, for the occasional case where you need to “tweak” the generated code.
The Eclipse IDE (see Recipe 1.3), for example, offers a Source
menu item
Generate hashCode() and equals()
—it will only do both at the same time, not let you generate equals()
without hashCode()
nor vice versa.
Here are the rules for a correct equals()
method:
x.equals(x)
must be true.
x.equals(y)
must be true if and only if y.equals(x)
is also true.
If x.equals(y)
is true and y.equals(z)
is true, then x.equals(z)
must also be true.
Multiple calls on x.equals(y)
return the same value (unless state values used in the comparison are changed, as by calling a set method).
x.equals(null)
must return false rather than accidentally throwing a NullPointerException
.
In addition, beware of one common mistake: the argument to equals()
must be
declared as java.lang.Object
, not the class it is in; this is so that
polymorphism will work correctly (some classes may not have an equals()
method of
their own). To prevent this mistake, the @Override
annotation is usually added to
the equals()
override, as mentioned in Recipe 15.3.
Here is a class that endeavors to implement these rules:
public
class
EqualsDemo
{
private
int
int1
;
private
SomeClass
obj1
;
/** Constructor */
public
EqualsDemo
(
int
i
,
SomeClass
o
)
{
int1
=
i
;
if
(
o
=
=
null
)
{
throw
new
IllegalArgumentException
(
"Data Object may not be null"
)
;
}
obj1
=
o
;
}
/** Default Constructor */
public
EqualsDemo
(
)
{
this
(
0
,
new
SomeClass
(
)
)
;
}
/** Demonstration "equals" method */
@Override
public
boolean
equals
(
Object
o
)
{
if
(
o
=
=
this
)
return
true
;
if
(
o
=
=
null
)
return
false
;
// Of the correct class?
if
(
o
.
getClass
(
)
!
=
EqualsDemo
.
class
)
return
false
;
EqualsDemo
other
=
(
EqualsDemo
)
o
;
// OK, cast to this class
// compare field-by-field
if
(
int1
!
=
other
.
int1
)
// compare primitives directly
return
false
;
if
(
!
obj1
.
equals
(
other
.
obj1
)
)
// compare objects using their equals
return
false
;
return
true
;
}
// ...
Optimization: if same object, true by definition.
If other object null, false by definition.
Compare class descriptors using !=; see following paragraph.
Optimization: compare primitives first. May or may not be worthwhile; may be better to order by those most likely to differ—depends on the data and the usage.
Another common mistake to avoid: note the use of class descriptor equality
(i.e., o.getClass() != EqualsDemo.class
) to ensure the correct class,
rather than via instanceof
, as is sometimes erroneously done.
The reflexive requirement of the equals()
method contract pretty much
makes it impossible to compare a subclass with a superclass correctly,
so we now use class equality (see Chapter 17, Reflection, or “A Class Named Class” for details
on the class descriptor).
Here is a basic JUnit
test (see Recipe 1.10) for the EqualsDemo
class:
/** Some JUnit test cases for EqualsDemo.
* Writing a full set is left as "an exercise for the reader".
*/
public
class
EqualsDemoTest
{
/** an object being tested */
EqualsDemo
d1
;
/** another object being tested */
EqualsDemo
d2
;
/** Method to be invoked before each test method */
@Before
public
void
setUp
()
{
d1
=
new
EqualsDemo
();
d2
=
new
EqualsDemo
();
}
@Test
public
void
testSymmetry
()
{
assertTrue
(
d1
.
equals
(
d1
));
}
@Test
public
void
testSymmetric
()
{
assertTrue
(
d1
.
equals
(
d2
)
&&
d2
.
equals
(
d1
));
}
@Test
public
void
testCaution
()
{
assertFalse
(
d1
.
equals
(
null
));
}
}
With all that testing, what could go wrong? Well, some things still need care. What if the object is a subclass of EqualsDemo
? We should test that it returns false in this case.
What else could go wrong? Well, what if either obj1
or other.obj1
is null? You might have just earned a nice shiny new NullPointerException
. So you also need to test for any possible null values. Good constructors can avoid these NullPointerException
s, as I’ve tried to do in EqualsDemo
, or else test for them explicitly.
Finally, you should never override equals()
without also overriding hashCode()
, and the same
fields must take part in both computations.
The hashCode()
method is supposed to return an int
that should uniquely identify any set of values in objects of its class.
A properly written hashCode()
method will follow these rules:
hashCode(x)
must return the same int
when called repeatedly, unless set methods have been called.
If x.equals(y)
, then x.hashCode()
must == y.hashCode()
.
If !x.equals(y)
, it is not required that x.hashCode()
!= y.hashCode()
, but doing so may improve performance of hash tables (i.e., hashes may call hashCode()
before equals()
).
The default hashCode()
on the standard JDK returns a machine address, which conforms to the first rule. Conformance to the second and third rules depends, in part, on your equals()
method. Here is a program that prints the hashcodes of a small handful of objects:
public
class
PrintHashCodes
{
/** Some objects to hashCode() on */
protected
static
Object
[]
data
=
{
new
PrintHashCodes
(),
new
java
.
awt
.
Color
(
0x44
,
0x88
,
0xcc
),
new
SomeClass
()
};
public
static
void
main
(
String
[]
args
)
{
System
.
out
.
println
(
"About to hashCode "
+
data
.
length
+
" objects."
);
for
(
int
i
=
0
;
i
<
data
.
length
;
i
++)
{
System
.
out
.
println
(
data
[
i
].
toString
()
+
" --> "
+
data
[
i
].
hashCode
());
}
System
.
out
.
println
(
"All done."
);
}
}
What does it print?
> javac -d . oo/PrintHashCodes.java > java oo.PrintHashCodes About to hashCode 3 objects. PrintHashCodes@982741a0 --> -1742257760 java.awt.Color[r=68,g=136,b=204] --> -12285748 SomeClass@860b41ad --> -2046082643 All done. >
The hashcode value for the Color
object is interesting. It is actually computed as something like:
alpha<<24 + r<<16 + g<<8 + b
In this formula, r, g, and b are the red, green, and blue components, respectively, and alpha is the transparency. Each of these quantities is stored in 8 bits of a 32-bit integer. If the alpha value is greater than 128, the “high bit” in this word—having been set by shifting into the sign bit of the word—causes the integer value to appear negative when printed as a signed integer. Hashcode values are of type int
, so they are allowed to be negative.
The java.util.Observable
class (designed to implement the Model-View-Controller pattern with AWT or Swing applications) contains a private Vector
but no clone method to deep-clone it. Thus, Observable
objects cannot safely be cloned, ever!
This and several other issues around clone()
—such as the uncertainty of whether a given clone()
implementation is deep or shallow—suggest that clone()
was not as well thought out as might be. An alternative is simply to provide a “copy constructor” or similar method:
public
class
CopyConstructorDemo
{
public
static
void
main
(
String
[]
args
)
{
CopyConstructorDemo
object1
=
new
CopyConstructorDemo
(
123
,
"Hello"
);
CopyConstructorDemo
object2
=
new
CopyConstructorDemo
(
object1
);
if
(!
object1
.
equals
(
object2
))
{
System
.
out
.
println
(
"Something is terribly wrong..."
);
}
System
.
out
.
println
(
"All done."
);
}
private
int
number
;
private
String
name
;
/** Default constructor */
public
CopyConstructorDemo
()
{
}
/** Normal constructor */
public
CopyConstructorDemo
(
int
number
,
String
name
)
{
this
.
number
=
number
;
this
.
name
=
name
;
}
/** Copy Constructor */
public
CopyConstructorDemo
(
CopyConstructorDemo
other
)
{
this
.
number
=
other
.
number
;
this
.
name
=
other
.
name
;
}
// hashCode() and equals() not shown
You need to write a private class, or a class to be used in one other class at most.
Use a nonpublic class or an inner class.
A nonpublic class can be written as part of another class’s source file, but not inside that class. An inner class is Java terminology for a class defined inside another class. Inner classes were first popularized with early Java for use as event handlers for GUI applications, but they have a much wider application.
Inner classes can, in fact, be constructed in several contexts. An inner class defined as a member of a class can be instantiated anywhere in that class. An inner class defined inside a method can be referred to later only in the same method. Inner classes can also be named or anonymous. A named inner class has a full name that is compiler-dependent; the standard JVM uses a name like MainClass$InnerClass
for the resulting file. An anonymous inner class, similarly, has a compiler-dependent name; the JVM uses MainClass$1
, MainClass$2
, and so on.
These classes cannot be instantiated in any other context; any explicit attempt to refer to, say, OtherMainClass$InnerClass
, is caught at compile time:
main/src/main/java/oo/AllClasses.java
public
class
AllClasses
{
public
class
Data
{
int
x
;
int
y
;
}
public
void
getResults
(
)
{
JButton
b
=
new
JButton
(
"Press me"
)
;
b
.
addActionListener
(
new
ActionListener
(
)
{
public
void
actionPerformed
(
ActionEvent
evt
)
{
Data
loc
=
new
Data
(
)
;
loc
.
x
=
(
(
Component
)
evt
.
getSource
(
)
)
.
getX
(
)
;
loc
.
x
=
(
(
Component
)
evt
.
getSource
(
)
)
.
getY
(
)
;
System
.
out
.
println
(
"Thanks for pressing me"
)
;
}
}
)
;
}
}
/** Class contained in same file as AllClasses, but can be used * (with a warning) in other contexts. */
class
AnotherClass
{
// methods and fields here...
AnotherClass
(
)
{
// Inner class from above cannot be used here, of course
// Data d = new Data(); // EXPECT COMPILE ERROR
}
}
Inner class, can be used anywhere in class AllClasses
.
Anonymous inner class syntax, using new
with a type followed by (){
, a class body, and }
.
Compiler will assign a name; class will extend or implement the given type, as appropriate.
Nonpublic class; can be used in the main class, and (with warning) in other classes.
One issue is that the inner class retains a reference to the outer class.
If you want to avoid memory leaks if the inner class will be held
for a longer time than the outer, you can make the inner class be static
.
Inner classes implementing a single-method interface can be written in a much more concise fashion as lambda expressions (see Chapter 9).
You want to provide callbacks—that is, have unrelated classes call back into your code.
One way is to use a Java interface.
An interface is a class-like entity that can contain only abstract methods and final fields. As we’ve seen, interfaces are used a lot in Java! In the standard API, the following are a few of the commonly used interfaces:
Runnable
, Comparable
, and Cloneable
(in java.lang
)
List
, Set
, Map
, and Enumeration/Iterator
(in the Collections API; see Chapter 7).
ActionListener
, WindowListener
, and others in the GUI layer.
Driver
, Connection
, Statement
, and ResultSet
in JDBC; see
https://darwinsys.com/javadatase.
The “remote interface”—the contact between the client and the server—is specified as an Interface
(in RMI, CORBA, and EJB)
Suppose we are generating a building management system. To be energy efficient, we want to be able to remotely turn off (at night and on weekends) such things as room lights and computer monitors, which use a lot of energy. Assume we have some kind of “remote control” technology. It could be a commercial version of BSR’s house-light control technology X10, it could be Bluetooth or 802.11—it doesn’t matter. What matters is that we have to be very careful what we turn off. It would cause great ire if we turned off computer processors automatically—people often leave things running overnight. It would be a matter of public safety if we ever turned off the building emergency lighting.2
So we’ve come up with the design shown in Figure 8-1.
The code for these data classes is not shown (it’s pretty trivial) but it’s in the oo/interfaces directory of the online source. The top-level classes (i.e., BuildingLight
and Asset
) are abstract classes. You can’t instantiate them, because they don’t have any specific functionality. To ensure—both at compile time and at runtime—that we can never switch off the emergency lighting, we need only ensure that the class representing it, EmergencyLight
, does not implement the PowerSwitchable
interface.
Note that we can’t very well use direct inheritance here. No common ancestor class
includes both ComputerMonitor
and RoomLights
that doesn’t also include ComputerCPU
and EmergencyLight
. Use interfaces to define functionality in unrelated classes.
How we use these is demonstrated by the BuildingManagement
class; this class is not part
of the hierarchy shown in Figure 8-1, but uses a collection of Asset
objects
from that hierarchy.
Items that can’t be switched must nonetheless be in the database, for various purposes
(auditing, insurance, etc.). In the method that turns things off, the code is careful
to check whether each object in the database is an instance of the PowerSwitchable
interface. If so, the object is casted to PowerSwitchable
so that its powerDown()
method can be called. If not, the object is skipped, thus preventing any possibility of
turning out the emergency lights or shutting off a machine that is busy running Seti@Home,
downloading a big MP3 playlist, or performing system backups. The following code shows this set of classes in action:
public
class
BuildingManagement
{
List
<
Asset
>
things
=
new
ArrayList
<>();
/** Scenario: goodNight() is called from a timer Thread at 2200, or when
* we get the "shutdown" command from the security guard.
*/
public
void
goodNight
()
{
things
.
forEach
(
obj
->
{
if
(
obj
instanceof
PowerSwitchable
)
((
PowerSwitchable
)
obj
).
powerDown
();
});
}
// tag::functional[]
public
void
goodNightFunctional
()
{
things
.
stream
().
filter
(
obj
->
obj
instanceof
PowerSwitchable
)
.
forEach
(
obj
->
((
PowerSwitchable
)
obj
).
powerDown
());
}
// end::functional[]
// goodMorning() would be similar, but call each one's powerUp().
/** Add a Asset to this building */
public
void
add
(
Asset
thing
)
{
System
.
out
.
println
(
"Adding "
+
thing
);
things
.
add
(
thing
);
}
/** The main program */
public
static
void
main
(
String
[]
av
)
{
BuildingManagement
b1
=
new
BuildingManagement
();
b1
.
add
(
new
RoomLights
(
101
));
// control lights in room 101
b1
.
add
(
new
EmergencyLight
(
101
));
// and emerg. lights.
// add the computer on desk#4 in room 101
b1
.
add
(
new
ComputerCPU
(
10104
));
// and its monitor
b1
.
add
(
new
ComputerMonitor
(
10104
));
// time passes, and the sun sets...
b1
.
goodNight
();
}
}
When you run this program, it shows all the items being added, but only the
PowerSwitchable
ones being switched off:
> java oo.interfaces.BuildingManagement Adding RoomLights@2dc77f32 Adding EmergencyLight@2e3b7f32 Adding ComputerCPU@2e637f32 Adding ComputerMonitor@2f1f7f32 Dousing lights in room 101 Dousing monitor at desk 10104 >
You want each of a number of subclasses to provide its own version of one or more methods.
Make the method abstract in the parent class; this makes the compiler ensure that each subclass implements it.
A hypothetical drawing program uses a Shape
subclass for anything that is drawn. Shape
has an abstract method called computeArea()
that computes the exact area of the given shape:
public abstract class Shape { protected int x, y; public abstract double computeArea( ); }
A Rectangle
subclass, for example, has a computeArea()
that multiplies width times height and returns the result:
public class Rectangle extends Shape { double width, height; public double computeArea( ) { return width * height; } }
A Circle
subclass returns πr2:
public class Circle extends Shape { double radius; public double computeArea( ) { return Math.PI * radius * radius; } }
This system has a high degree of generality. In the main program, we can iterate over a collection of Shape
objects and—here’s the real beauty—call computeArea()
on any Shape
subclass object without having to worry about what kind of shape it is. Java’s polymorphic methods automatically call the correct computeArea( )
method in the class of which the object was originally constructed:
main/src/main/java/oo//shapes/ShapeDriver.java
/** Part of a main program using Shape objects */
public
class
ShapeDriver
{
Collection
<
Shape
>
allShapes
;
// created in a Constructor, not shown
/** Iterate over all the Shapes, getting their areas;
* this cannot use the Java 8 Collection.forEach because the
* variable total would have to be final, which would defeat the purpose :-)
*/
public
double
totalAreas
()
{
double
total
=
0.0
;
for
(
Shape
s
:
allShapes
)
{
total
+=
s
.
computeArea
();
}
return
total
;
}
Polymorphism is a great boon for software maintenance: if a new subclass is added, the code in the main program does not change. Further, all the code that is specific to, say, polygon handling, is all in one place: in the source file for the Polygon
class. This is a big improvement over older languages, where type fields in a structure were used with case or switch statements scattered all across the software. Java makes software more reliable and maintainable with the use of polymorphism.
You need to manage a small list of discrete values within a program.
Use the Java enum
mechanism.
To enumerate means to list all the values. You often know that a small list of possible values is all that’s wanted in a variable, such as the months of the year, the suits or ranks in a deck of cards, the primary and secondary colors, and so on. The C programming language provided an enum
keyword:
enum { BLACK, RED, ORANGE} color;
Java was criticized in its early years for its lack of enumerations, which many developers have wished for. Many have had to develop custom classes to implement the “typesafe enumeration pattern.”
But C enumerations are not “typesafe”; they simply define constants that can be used in any integer context. For example, this code compiles without warning, even on gcc 3 with -Wall
(all warnings), whereas a C++ compiler catches the error:3
enum { BLACK, RED, ORANGE} color; enum { READ, UNREAD } state; /*ARGSUSED*/ int main(int argc, char *argv[]) { color = RED; color = READ; // In C this will compile, give bad results return 0; }
To replicate this mistake in Java, one needs only to define a series
of final int
values; it will still not be typesafe. By typesafe I
mean that you cannot accidentally use values other than those
defined for the given enumeration. The definitive statement on the
“typesafe enumeration pattern” is probably the version defined in
Item 21 of Joshua Bloch’s book Effective Java (Addison-Wesley).
All modern Java versions include enumerations in the language; it is no longer necessary to use the code from Bloch’s book. Bloch was one of the authors of the Typesafe Enumeration specification
(enum
keyword), so you can be sure that Java now does a good job of
implementing his pattern. These enum
s are implemented as classes,
subclassed (transparently, by the compiler) from the class
java.lang.Enum
. Unlike C, and unlike the “series of final int”
implementation, Java typesafe enumerations:
Are printable (they print as the name, not as an underlying int
implementation).
Are almost as fast as int
constants, but the code is more readable.
Can be easily iterated over.
Utilize a separate namespace for each enum
type, so you don’t have to prefix each with some sort of constant name, like ACCOUNT_SAVINGS
, ACCOUNT_CHECKING
, etc.
Enum constants are not compiled into clients, giving you the freedom
to reorder the constants within your enum
without recompiling the
client classes. That does not mean you should, however; think about the
case where objects that use them have been persisted, and the person
designing the database mapping used the numeric values of the enums.
Bad idea to reorder then!
Additionally, an enum
type is a class, so it can, for example, implement arbitrary
interfaces, and you can add constructors, fields, and methods to an
enum
class.
Compared to Bloch’s Typesafe Enum pattern in the book:
Java enums
are simpler to use and more readable (those in the book require a lot of methods, making them cumbersome to write).
Enums can be used in switch statements.
So there are many benefits and few pitfalls.
The enum
keyword is at the same level as the keyword class
in declarations. That is, an enum
may be declared in its own file with public or default access. It may also be declared inside classes, much like nested or inner classes (see Recipe 8.2). Media.java, shown in Example 8-1, is a code sample showing the definition of a typesafe enum
.
public
enum
Media
{
BOOK
,
MUSIC_CD
,
MUSIC_VINYL
,
MOVIE_VHS
,
MOVIE_DVD
;
}
Notice that an enum
class is a class; see what javap thinks of the Media
class:
C:> javap Media Compiled from "Media.java" public class Media extends java.lang.Enum{ public static final Media BOOK; public static final Media MUSIC_CD; public static final Media MUSIC_VINYL; public static final Media MOVIE_VHS; public static final Media MOVIE_DVD; public static final Media[] values( ); public static Media valueOf(java.lang.String); public Media(java.lang.String, int); public int compareTo(java.lang.Enum); public int compareTo(java.lang.Object); static {}; } C:>
Product.java, shown in Example 8-2, is a code sample that uses the Media
enum.
public
class
Product
{
String
title
;
String
artist
;
Media
media
;
public
Product
(
String
artist
,
String
title
,
Media
media
)
{
this
.
title
=
title
;
this
.
artist
=
artist
;
this
.
media
=
media
;
}
@Override
public
String
toString
()
{
switch
(
media
)
{
case
BOOK:
return
title
+
" is a book"
;
case
MUSIC_CD:
return
title
+
" is a CD"
;
case
MUSIC_VINYL:
return
title
+
" is a relic of the age of vinyl"
;
case
MOVIE_VHS:
return
title
+
" is on old video tape"
;
case
MOVIE_DVD:
return
title
+
" is on DVD"
;
default
:
return
title
+
": Unknown media "
+
media
;
}
}
}
In Example 8-3, MediaFancy
shows how operations (methods) can be added to enumerations; the toString()
method is overridden for the “book” value of this enum.
/** An example of an enum with method overriding */
public
enum
MediaFancy
{
/** The enum constant for a book, with a method override */
BOOK
{
public
String
toString
()
{
return
"Book"
;
}
},
/** The enum constant for a Music CD */
MUSIC_CD
,
/** ... */
MUSIC_VINYL
,
MOVIE_VHS
,
MOVIE_DVD
;
/** It is generally disparaged to have a main() in an enum;
* please forgive this tiny demo class for doing so.
*/
public
static
void
main
(
String
[]
args
)
{
MediaFancy
[]
data
=
{
BOOK
,
MOVIE_DVD
,
MUSIC_VINYL
};
for
(
MediaFancy
mf
:
data
)
{
System
.
out
.
println
(
mf
);
}
}
}
Running the MediaFancy
program produces this output:
Book MOVIE_DVD MUSIC_VINYL
That is, the Book
values print in a “user-friendly” way compared to the default way the other values print. In real life you’d want to extend this to all the values in the enum
.
Finally, EnumList
, in Example 8-4, shows how to list all the possible values that a given enum
can take on; simply iterate over the array returned by the enum class’s inherited values()
method.
public
class
EnumList
{
enum
State
{
ON
,
OFF
,
UNKNOWN
}
public
static
void
main
(
String
[]
args
)
{
for
(
State
i
:
State
.
values
())
{
System
.
out
.
println
(
i
);
}
}
}
The output of the EnumList
program is, of course:
ON OFF UNKNOWN
Optional
You worry about null references causing NullPointerException (NPE) in your code.
Use java.util.Optional
The developer who invented the notion of null pointers,
and a key early contributor to our discipline, has described the null
reference as
“my billion-dollar mistake”.
However, use of null
is not going away anytime soon.
What we can do is make clear that we worry about them in certain contexts.
For this purpose, Java 8 introduced the class java.util.Optional
.
The Optional
is an object wrapper around a possibly-null object reference.
The “Optional” wrapper has a long history;
a similar construct is found in LLVM’s ADT, where its Optional
describes
itself in turn as “in the spirit of OCaml’s opt variant”.
Optionals can be created with one of the creational methods:
Optional.empty()
returns an empty optional
Optional.of(T obj)
returns a non-empty optional containing the given value
Optional.ofNullable(T obj)
returns either an empty Optional or one containing the given value.
The basic operation of this class is to behave in one of two ways depending on whether it is full or empty. Optional objects are immutable so they cannot transition from one state to the other.
The simplest use is to invoke isEmpty()
or its opposite isPresent()
and use program logic to behave differently.
Not much different from using an if
statement to check for null,
but it puts the choice in front of you,
making it less likely that you’ll forget to check.
jshell
>
Optional
<
String
>
opt
=
Optional
.
of
(
"What a day!"
);
opt
==>
Optional
[
What
a
day
!]
jshell
>
if
(
opt
.
isPresent
())
{
...>
System
.
out
.
println
(
"Value is "
+
opt
.
get
());
...>
}
else
{
...>
System
.
out
.
println
(
"Value is not present."
);
...>
}
Value
is
What
a
day
!
A better form would use the orElse
method:
jshell
>
System
.
out
.
println
(
"Value is "
+
opt
.
orElse
(
"not present"
));
Value
is
What
a
day
!
A useful use case is that of passing values into methods.
The object can be wrapped in an Optional either before it is passed
to a method or after; the latter is useful when migrating from code
that didn’t use Optional from the start.
This Item
demo might represent part of a shipments tracking program,
a lending library manager, or anything else that has time-related data
which might be missing.
List
.
of
(
new
Item
(
"Item 1"
,
LocalDate
.
now
().
plusDays
(
7
)),
new
Item
(
"Item 2"
)).
forEach
(
System
.
out
::
println
);
static
class
Item
{
String
name
;
Optional
<
LocalDate
>
dueDate
;
Item
(
String
name
)
{
this
(
name
,
null
);
}
Item
(
String
name
,
LocalDate
dueDate
)
{
this
.
name
=
name
;
this
.
dueDate
=
Optional
.
ofNullable
(
dueDate
);
}
public
String
toString
()
{
return
String
.
format
(
"%s %s"
,
name
,
dueDate
.
isPresent
()
?
"Item is due on "
+
dueDate
.
get
()
:
"Sorry, do not know when item is due"
);
}
}
There are methods that throw exceptions, that return null, and so on.
Also methods for interacting with the Streams mechanism (see
Recipe 9.4).
A full list of Optional
’s methods is at the start of
the javadoc page.
You want to be sure there is only one instance of your class in a given Java Virtual Machine, or at least within your application.
There are several methods of making your class enforce the Singleton Pattern
Enum implementation;
Having only a private constructor(s) and a getInstance() method;
Use a frameworks such as Spring or CDI (Recipe 8.8) configured to give Singleton-style instantiation of plain classes.
It is often useful to ensure that only one instance of a class gets created, usually to funnel all requests for some resource through a single point. An example of a Singleton from the standard API is java.lang.Runtime
; you cannot create instances of Runtime
, you simply ask for a reference by calling the static method Runtime.getRuntime()
. Singleton is also an example of a design pattern that can be easily implemented.
In all forms, the point of the singleton implementation is to provide an instance
in which certain methods can run, typically to control access to some reasource.
The easiest implementation uses a Java enum
to provide singletonness.
The enum
mechanism already guarantees that only one instance of each enum constant
will exist in a given JVM context, so this technique piggy-backs on that.
public
enum
EnumSingleton
{
INSTANCE
;
// instance methods protected by singleton-ness would be here...
/** A simple demo method */
public
String
demoMethod
()
{
return
"demo"
;
}
}
Using it is simple:
// Demonstrate the enum method: EnumSingleton.INSTANCE.demoMethod();
The next easiest implementation consists of a private constructor and a field to hold its result, and a static accessor method with a name like getInstance()
.
The private field can be assigned from within a static initializer block or, more simply, using an initializer. The getInstance()
method (which must be public) then simply returns this instance:
public
class
Singleton
{
/**
* Static Initializer is run before class is available to code, avoiding
* broken anti-pattern of lazy initialization in instance method.
* For more complicated construction, could use static block initializer.
*/
private
static
Singleton
instance
=
new
Singleton
();
/** A private Constructor prevents any other class from instantiating. */
private
Singleton
()
{
// nothing to do this time
}
/** Static 'instance' method */
public
static
Singleton
getInstance
()
{
return
instance
;
}
// other methods protected by singleton-ness would be here...
/** A simple demo method */
public
String
demoMethod
()
{
return
"demo"
;
}
}
Note that the method of using “lazy evaluation” in the getInstance()
method (which is advocated in Design Patterns), is not necessary in Java because Java already uses “lazy loading.” Your Singleton
class will probably not get loaded until its getInstance()
is called, so there is no point in trying to defer the singleton construction until it’s needed by having getInstance()
test the singleton variable for null and creating the singleton there.
Using this class is equally simple: simply get the instance reference, and invoke methods on it:
// Demonstrate the codeBased method:
Singleton
.
getInstance
().
demoMethod
();
Some commentators believe that a code-based Singleton should also provide a public final clone()
method that just throws an exception, to avoid subclasses that “cheat” and clone()
the
singleton. However, it is clear that a class with only a private constructor cannot be
subclassed, so this paranoia does not appear to be necessary.
The Collections
class in java.util
has methods singletonList()
, singletonMap()
, and singletonSet()
, which give out an immutable List
, Map
, or Set
, respectively, containing only the one object that is passed to the method. This does not, of course, convert the object into a Singleton in the sense of preventing that object from being cloned or other instances from being constructed.
See page 127 of the original Design Patterns book.
You’d like to use an application-specific exception class or two.
Go ahead and subclass Exception
or RuntimeException
.
In theory, you could subclass Throwable
directly, but that’s considered rude. You normally subclass Exception
(if you want a checked exception) or RuntimeException
(if you want an unchecked exception). Checked exceptions are those that an application developer is required to catch or “throw upward” by listing them in the throws
clause of the invoking method.
When subclassing either of these, it is customary to provide at least these constructors:
A no-argument constructor
A one-string argument constructor
A two argument constructor—a string message and a Throwable
“cause”
The “cause” will appear if the code receiving the exception performs a stack trace operation on it,
with the prefix “Root Cause is” or similar. Example 8-7 shows these three constructors for an application-defined exception, ChessMoveException:
.
/** A ChessMoveException is thrown when the user makes an illegal move. */
public
class
ChessMoveException
extends
Exception
{
private
static
final
long
serialVersionUID
=
802911736988179079L
;
public
ChessMoveException
()
{
super
();
}
public
ChessMoveException
(
String
msg
)
{
super
(
msg
);
}
public
ChessMoveException
(
String
msg
,
Exception
cause
)
{
super
(
msg
,
cause
);
}
}
The javadoc documentation for Exception
lists a large number of subclasses; you might look there first to see if there is one you can use.
You want to avoid excessive coupling between classes, and you want to avoid excessive code dedicated to object creation/lookup.
Use a Dependency Injection Framework.
A Dependency Injection Framework allows you to have objects “passed in” to your code instead of making you either create them explicitly (which ties your code to the implementing class name, since you’re calling the constructor) or looking for them (which requires use of a possibly cumbersome lookup API, such as JNDI, the Java Naming and Directory Interface).
Three of the best-known Dependency Injection Frameworks are the Spring Framework, the Java Enterprise Edition’s Context and Depency Injection (CDI), and Google Guice. Suppose we have three classes, a Model, a View, and a Controller, implementing the traditional MVC pattern. Given that we may want to have different versions of some of these, especially the View, we’ll define Java interfaces for simple versions of the Model and View, shown in the following code:
public
interface
Model
{
String
getMessage
();
}
public
interface
View
{
void
displayMessage
();
}
The implementations of these are not shown, because they’re so trivial, but they are online. The Controller in this example is a main program, no interface needed. First, a version of the main program not using Dependency Injection. Obviously the View requires the Model, to get the data to display:
main/src/main/java/di/ControllerTightlyCoupled.java
public
class
ControllerTightlyCoupled
{
public
static
void
main
(
String
[]
args
)
{
Model
m
=
new
SimpleModel
();
ConsoleViewer
v
=
new
ConsoleViewer
();
v
.
setModel
(
m
);
v
.
displayMessage
();
}
}
Here we have four tasks to undertake:
Create the Model.
Create the View.
Tie the Model into the View.
Ask the View to display some data.
Now a version using Dependency Injection:
main/src/main/java/di/spring/MainAndController.java - Spring Controller
public
class
MainAndController
{
public
static
void
main
(
String
[]
args
)
{
ApplicationContext
ctx
=
new
AnnotationConfigApplicationContext
(
"di.spring"
);
View
v
=
ctx
.
getBean
(
"myView"
,
View
.
class
);
v
.
displayMessage
();
((
AbstractApplicationContext
)
ctx
).
close
();
}
}
In this version, we have only three tasks:
Set up the Spring “context,” which provides the dependency injection framework.
Get the View from the context; it already has the Model set into it!
Ask the View to display some data.
Furthermore, we don’t depend on particular implementations of the interface.
How does Spring know to “inject” or provide a Model to the View? And how does it know what code to use for the View? There might be multiple implementations of the View interface. Of course we have to tell it these things, which we’ll do here with annotations:
@Named
(
"myView"
)
public
class
ConsoleViewer
implements
View
{
Model
messageProvider
;
@Override
public
void
displayMessage
()
{
System
.
out
.
println
(
messageProvider
.
getMessage
());
}
@Resource
(
name
=
"myModel"
)
public
void
setModel
(
Model
messageProvider
)
{
this
.
messageProvider
=
messageProvider
;
}
}
While Spring has provided its own annotations, it will also accept the
Java standard @javax.annotation.Resource
annotation for injection
and @java.inject.Named
to specify the injectee.
Due to the persistence of information on the Web, if you do a web search for Spring Injection, you will probably find zillions of articles that refer to the older Spring 2.x way of doing things, which is to use an XML configuration file. You can still use this, but modern Spring practice is generally to use Java annotations to configure the dependencies.
Annotations are also used in the Java Enterprise Edition Contexts and Dependency Injection (CDI). Although this is most widely used in web applications, we’ll reuse the same example, using the open source “Weld” implementation of CDI. CDI is quite a bit more powerful than Spring’s DI; because in CDI we don’t even need to know the class from which a resource is being injected, we don’t even need the interfaces from the Spring example! First, the “Controller” or main program, which requires a Weld-specific import or two because CDI was originally designed for use in enterprise applications:
public
class
MainAndController
{
public
static
void
main
(
String
[]
args
)
{
final
Instance
<
Object
>
weldInstance
=
new
Weld
().
initialize
().
instance
();
weldInstance
.
select
(
ConsoleViewer
.
class
).
get
().
displayMessage
();
}
}
The View
interface is shared between both implementations.
The ConsoleViewer
implementation is similar too, except it isn’t coupled to the Model;
it just asks to have a String
injected.
In this simple example there is only one String
in the application; in a larger app
you would need one additional annotation to specify which string to inject.
Here is the CDI ConsoleViewer:
public
class
ConsoleViewer
implements
View
{
@Inject
@MyModel
private
String
message
;
@Override
public
void
displayMessage
()
{
System
.
out
.
println
(
message
);
}
}
Where does the injected String
come from? From the Model, as before:
main/src/main/java/di/cdi/ModelImpl.java
public
class
ModelImpl
{
public
@Produces
@MyModel
String
getMessage
(
InjectionPoint
ip
)
throws
IOException
{
ResourceBundle
props
=
ResourceBundle
.
getBundle
(
"messages"
);
return
props
.
getString
(
ip
.
getMember
().
getDeclaringClass
().
getSimpleName
()
+
"."
+
ip
.
getMember
().
getName
());
}
}
Spring DI, Java EE CDI, and Guice all provide powerful “dependency injection.”
Spring’s is more widely used; Java EE’s has the same power and is built into every EE container..
All three can be used standalone or in a web application, with minor variations.
In the EE, Spring provides special support for web apps, and in EE containers,
CDI is already set up so the first statement in the CDIMain
example is
not needed in an EE app. There are many books on Spring. One book specifically treats Weld: JBoss Weld CDI for Java Platform
by Ken Finnegan (O’Reilly).
Not because it is very sophisticated, but because it is simple, this program serves as an example of some of the things we’ve covered in this chapter, and also, in its subclasses, provides a springboard for other discussions. This class describes a series of old-fashioned (i.e., common in the 1970s and 1980s) pen plotters. A pen plotter, in case you’ve never seen one, is a device that moves a pen around a piece of paper and draws things. It can lift the pen off the paper or lower it, and it can draw lines, letters, and so on. Before the rise of laser printers and ink-jet printers, pen plotters were the dominant means of preparing charts of all sorts, as well as presentation slides (this was, ah, well before the rise of programs like Harvard Presents and Microsoft PowerPoint). Today, few, if any, companies still manufacture pen plotters, but I use them here because they are simple enough to be well understood from this brief description. Today’s “3-D Printers” may be thought of as representing a resurgence of the pen plotter with just one additional axis of motion. And a fancier pen.
I’ll present a high-level class that abstracts the key characteristics of a series of such
plotters made by different vendors. It would be used, for example, in an analytical or
data-exploration program to draw colorful charts showing the relationships found in data.
But I don’t want my main program to worry about the gory details of any particular brand
of plotter, so I’ll abstract into a Plotter
class, whose source is as follows:
main/src/main/java/plotter/Plotter.java
/**
* Plotter abstract class. Must be subclassed
* for X, DOS, Penman, HP plotter, etc.
*
* Coordinate space: X = 0 at left, increases to right.
* Y = 0 at top, increases downward (same as AWT).
*
* @author Ian F. Darwin
*/
public
abstract
class
Plotter
{
public
final
int
MAXX
=
800
;
public
final
int
MAXY
=
600
;
/** Current X co-ordinate (same reference frame as AWT!) */
protected
int
curx
;
/** Current Y co-ordinate (same reference frame as AWT!) */
protected
int
cury
;
/** The current state: up or down */
protected
boolean
penUp
;
/** The current color */
protected
int
penColor
;
Plotter
()
{
penUp
=
true
;
curx
=
0
;
cury
=
0
;
}
abstract
void
rmoveTo
(
int
incrx
,
int
incry
);
abstract
void
moveTo
(
int
absx
,
int
absy
);
abstract
void
penUp
();
abstract
void
penDown
();
abstract
void
penColor
(
int
c
);
abstract
void
setFont
(
String
fName
,
int
fSize
);
abstract
void
drawString
(
String
s
);
/* Concrete methods */
/** Draw a box of width w and height h */
public
void
drawBox
(
int
w
,
int
h
)
{
penDown
();
rmoveTo
(
w
,
0
);
rmoveTo
(
0
,
h
);
rmoveTo
(-
w
,
0
);
rmoveTo
(
0
,
-
h
);
penUp
();
}
/** Draw a box given an AWT Dimension for its size */
public
void
drawBox
(
java
.
awt
.
Dimension
d
)
{
drawBox
(
d
.
width
,
d
.
height
);
}
/** Draw a box given an AWT Rectangle for its location and size */
public
void
drawBox
(
java
.
awt
.
Rectangle
r
)
{
moveTo
(
r
.
x
,
r
.
y
);
drawBox
(
r
.
width
,
r
.
height
);
}
/** Show the current location; useful for
* testing, if nothing else.
*/
public
Point
getLocation
()
{
return
new
Point
(
curx
,
cury
);
}
}
Note the variety of abstract methods. Those related to motion, pen control, or drawing are left abstract, due to the number of different ways of implementing motion on radically-different devices. However, the method for drawing a rectangle (drawBox
) has a default implementation, which simply puts the currently selected pen onto the paper at the last-moved-to location, draws the four sides, and raises the pen. Subclasses for “smarter” plotters will likely override this method, but subclasses for less-evolved plotters will probably use the default version. This method also has two overloaded convenience methods for cases where the client has an AWT Dimension for the size or an AWT Rectangle for the location and size.
To demonstrate one of the subclasses of this program, consider the following simple “driver” program. This is intended to simulate a larger graphics application such as GnuPlot.
The Class.forName()
near the beginning of main
is discussed in Recipe 17.2; for now, you can take my word that it simply creates an instance of the given subclass, which we store in a Plotter
reference named “r” and use to draw the plot:
main/src/main/java/plotter/PlotDriver.java
public
class
PlotDriver
{
/** Construct a Plotter driver, and try it out. */
public
static
void
main
(
String
[]
argv
)
{
Plotter
r
;
if
(
argv
.
length
!=
1
)
{
System
.
err
.
println
(
"Usage: PlotDriver driverclass"
);
return
;
}
try
{
Class
<?>
c
=
Class
.
forName
(
argv
[
0
]);
Object
o
=
c
.
newInstance
();
if
(!(
o
instanceof
Plotter
))
throw
new
ClassNotFoundException
(
"Not instanceof Plotter"
);
r
=
(
Plotter
)
o
;
}
catch
(
ClassNotFoundException
e
)
{
System
.
err
.
println
(
"Sorry, class "
+
argv
[
0
]
+
" not a plotter class"
);
return
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
;
}
r
.
penDown
();
r
.
penColor
(
1
);
r
.
moveTo
(
200
,
200
);
r
.
penColor
(
2
);
r
.
drawBox
(
123
,
200
);
r
.
rmoveTo
(
10
,
20
);
r
.
penColor
(
3
);
r
.
drawBox
(
123
,
200
);
r
.
penUp
();
r
.
moveTo
(
300
,
100
);
r
.
penDown
();
r
.
setFont
(
"Helvetica"
,
14
);
r
.
drawString
(
"Hello World"
);
r
.
penColor
(
4
);
r
.
drawBox
(
10
,
10
);
}
}
We’ll see example subclasses of this Plotter
class in upcoming chapters.
1 A value class is one used mainly to hold state, rather than logic: a Person
is a value class, whereas java.lang.Math
is not. Many classes are somewhere in between.
2 Of course these lights wouldn’t have remote power-off. But the computers might, for maintenance purposes.
3 For Java folks not that familiar with C/C++, C is the older, non-OO language; C++ is an OO derivative of C; and Java is in part a portable, more strongly typesafe derivative of C++.
3.145.8.8