Sometimes, it is useful for a class to
keep track of how many instances have been created. This is typically done by
having its constructors increment a private static field. In the program that
follows, the Creature class demonstrates this technique, and the
Creator class exercises it, printing the number of Creature
instances it has created. What does the program print?
public class Creator { public static void main(String[] args) { for (int i = 0; i < 100; i++) Creature creature = new Creature(); System.out.println(Creature.numCreated()); } } class Creature { private static long numCreated = 0; public Creature() { numCreated++; } public static long numCreated() { return numCreated; } }
Solution 55: Creationism
This is a trick question. The program looks as though it ought
to print 100, but it doesn't print anything, because it doesn't
compile. If you tried to compile it, you may have found the compiler diagnostics
to be less than helpful. This is what javac prints:
Creator.java:4: not a statement Creature creature = new Creature(); ^ Creator.java:4: ';' expected Creature creature = new Creature(); ^
A local variable declaration looks like a statement but
technically speaking is not; it is a local variable
declaration statement [JLS 14.4]. The syntax of the language does not
allow a local variable declaration statement as the statement repeated by a
for, while, or do loop [JLS 14.12-14]. A local variable declaration can appear only as a statement
directly within a block. (A block is a pair of curly braces and the
statements and declarations contained within it.)
There are two ways to fix the problem. The obvious way is to
place the declaration in a block:
for (int i = 0; i < 100; i++) { Creature creature = new Creature(); }
Note, however, that the program is not using the local variable
creature. Therefore, it makes more sense to replace the declaration
with a naked constructor invocation, emphasizing that the reference to the newly
created object is being discarded:
for (int i = 0; i < 100; i++) new Creature();
If either of these changes is made, the program will print
100 as expected.
Note that the variable used to keep track of the number of
Creature instances (numCreated) is a long rather than
an int. It is quite conceivable that a program might create more
instances of some class than the maximum int value but not the maximum
long value. The maximum int value is 231 - 1, or
about 2.1 x 109; the maximum long value is 263 -
1, or about 9.2 x 1018. Today, it is possible to create about
108 objects per second, which means that a program would have to run
about three thousand years before a long object counter would overflow.
Even in the face of increasing hardware speeds, long object counters
should be adequate for the foreseeable future.
Also note that the creation counting strategy in this puzzle is
not thread-safe. If multiple threads can create objects in parallel, the code to
increment the counter and the code to read it must be synchronized:
// Thread-safe creation counter class Creature { private static long numCreated; public Creature() { synchronized (Creature.class) { numCreated++; } } public static synchronized long numCreated() { return numCreated; } }
Alternatively, if you are using release 5.0 or a later release,
you can use an AtomicLong instance, which obviates the need for
synchronization in the face of concurrency.
// Thread-safe creation counter using AtomicLong;
import java.util.concurrent.atomic.AtomicLong;
class Creature {
private static AtomicLong numCreated = new AtomicLong();
public Creature() {
numCreated.incrementAndGet();
}
public static long numCreated() {
return numCreated.get();
}
}
Note that it is not sufficient
to declare numCreated to be volatile. The volatile modifier
guarantees that other threads will see the most recent value assigned to a
field, but it does not make the increment operation atomic.
In summary, a local variable declaration cannot be used as the
repeated statement in a for, while, or do loop but
can appear only as a statement directly within a block. Also, when using a variable to count instance creations, use a
long rather than an int, to prevent overflow. Finally,
if you are going to create instances in multiple threads, either synchronize
access to the instance counter or use an AtomicLong.
No comments:
Post a Comment
Your comments are welcome!