Java Code Talk, Part 4
Welcome to Code Talk with us, Click and Hack the type-it brothers. There are a few questions that we get asked all the time. We thought we'd answer them once and for all:
- It seemed like the thing to do at the time.
- He's click; I'm hack.
- No.
- Yes.
- Boxers.
Neal: Last week's first puzzler was to figure out what the following program prints:
public class Truth { public static void main(String args[]) throws Throwable { new Foo(); } } class Foo { static Bar b = new Bar(); static boolean truth() { return true; } static final boolean TRUTH = truth(); Foo() { System.out.println("The truth is: " + TRUTH); } } class Bar extends Foo { }
If you tried running the program, you found out that it prints:
The truth is: false The truth is: true
Josh: Hang on a sec, are you saying the program observes a final variable changing its value? I thought the whole point of final was that you could never change its value.
Neal: Yes, but you can observe the transition when the first (and only) assignment takes place, and this program does. Let's trace its execution.
Josh: I'd rather see your execution.
Neal: Never mind that. The main program creates a Foo, which causes class Foo to be initialized. The static initializers in Foo are processed in source order, beginning with the initialization of the field b. The initializer for b constructs a new Bar, so class Bar is intitialized. Normally, before a class is initialized its superclass is initialized, but Bar's superclass is Foo, which we're already in the midst of initializing.
The VM spec says that in this case we skip the initialization of Foo that was triggered by Bar. That means that when we run Bar's constructor, the inherited static field TRUTH hasn't been initialized yet and contains its default initial value of false. So the program prints "The truth is: false". Then the static initialization of Foo completes by initializing the variable TRUTH to true.
Finally, the constructor from the main program is called. By then, all of the static initialization has taken place, so the program prints "The truth is: true".
Josh: It's too bad the VM doesn't throw an error when this happens. I'd rather get a runtime error than allow my program to use classes before they're initialized.
Neal: Perhaps, but it's too late to change the semantics of the language. This puzzler illustrates a problem in the design of many programming languages: coming up with a consistent, meaningful initialization sequence in the face of possible circularities.
The lesson for programmers is to avoid complex initialization sequences. In particular it's usually wrong to initialize a static field with an instance of a subclass. Unfortunately, this pattern arises naturally in the context of service provider frameworks. If you find yourself doing this, consider initializing the static field lazily. Techniques for lazy initialization are discussed in Item 48 of my brother's book, Effective Java™ Programming Language Guide.
Josh: Last week's second puzzler was to figure out what the following program prints (and why):
public class Shifty { public static void main(String[] args) { int n = 0; while (-1 << n != 0) n++; System.out.println(n); } }
Neal: This is just the same as asking how many bits there are in an integer, so it prints 32, right? Negative one is an integer with all 32 bits set. Since the << operator shifts zeroes in on the right, once you shift off all 32 of the one bits, the result is zero.
Josh: You would think so, but that's not right. The shift operators only use the five low-order bits of the right operand for the shift distance (six bits if the left operand is a long), so the shift distance is always between 0 and 31 (0 and 63 for long). In effect, the right operand is taken mod 32 (mod 64 for a long). Attempting to shift an int 32 bits just returns the int itself. There is no shift distance that discards all 32 bits in an int. So this is an infinite loop; it doesn't print anything.
Neal: Tricky.
Josh: OK, time for this week's puzzlers. If you attended our JavaOneSM Puzzlers talk, or read my book, you know that classes that override equals have to override hashCode too or they won't work. The class below does override hashCode, but it still doesn't work. What's wrong with it?
import java.util.*; public class Name { public static void main(String[] args) { Set s = new HashSet(); s.add(new Name("Donald", "Duck")); System.out.println(s.contains(new Name("Donald", "Duck"))); } private String first, last; public Name(String first, String last) { if (first == null || last == null) throw new NullPointerException(); this.first = first; this.last = last; } public boolean equals(Name other) { return first.equals(other.first) && last.equals(other.last); } public int hashCode() { return 31 * first.hashCode() + last.hashCode(); } }
Neal: Good question. Here's one more. What does this program print?
public class Search { static int[] specialNums = { 1, 5, 10, 15, 37, 102, 776, 12 }; static boolean isSpecial(int n) { try { for (int i=0; i < specialNums.length; i++) if (specialNums[i] == n) return true; } finally { return false; } } public static void main(String[] args) { System.out.println(isSpecial(16)); System.out.println(isSpecial(12)); } }
Josh: Good question. Tune in next week for some lame answers. And send your puzzlers to javapuzzlers@sun.com.