The behavior of this program changed between release 1.4 of
the Java platform and release 5.0. What does the program do under each of these
releases? (If you have access only to release 5.0, you can emulate the 1.4
behavior by compiling with the -source 1.4 flag.)
import java.util.Random;
public class CoinSide {
private static Random rnd = new Random();
public static CoinSide flip() {
return rnd.nextBoolean() ?
Heads.INSTANCE : Tails.INSTANCE;
}
public static void main(String[] args) {
System.out.println(flip());
}
}
class Heads extends CoinSide {
private Heads() { }
public static final Heads INSTANCE = new Heads();
public String toString() {
return "heads";
}
}
class Tails extends CoinSide {
private Tails() { }
public static final Tails INSTANCE = new Tails();
public String toString() {
return "tails";
}
}
Solution 75: Heads or Tails?
This program doesn't appear to use any
release 5.0 features at all, so it is difficult to see why there should be any
difference in behavior. In fact, the program fails to compile in release 1.4 or
any earlier release:
CoinSide.java:7:
incompatible types for ?: neither is a subtype of the other
second operand: Heads
third operand : Tails
return rnd.nextBoolean() ?
^
The behavior of the conditional (? :) operator was
more restrictive before release 5.0 [JLS2 15.25]. When both the second and third
operands were of a reference type, the conditional operator required that one of
them be a subtype of the other. As neither class Heads nor
Tails is a subtype of the other, there is an error. To get this code to
compile, you could cast one of the operands to the common supertype:
return rnd.nextBoolean() ?
(CoinSide)Heads.INSTANCE : Tails.INSTANCE;
In release 5.0 and later releases, the language is much more
forgiving. The conditional operator is always legal when its second and third
operands have reference types. The result type is the least common supertype of these two types. A common
supertype always exists, because Object is the supertype of every
object type. As a practical matter, the main consequence of this change is that
the conditional operator just does the right thing more often and gives
compile-time errors less often. For the language nerds among us, the
compile-time type of the result of the conditional operator for reference types
is the same as the result of invoking the following method on the second and
third operands [JLS 15.25]:
<T> T choose(T a, T b) { }
The problem illustrated by this puzzle did come up fairly often
under release 1.4 and earlier releases, forcing you to insert casts that served
merely to obscure the purpose of your code. That said, the puzzle itself is
artificial. Before release 5.0, it would have been more natural to write
CoinSide using the Typesafe Enum pattern [EJ Item 21]:
import java.util.Random;
public class CoinSide {
public static final CoinSide HEADS = new CoinSide("heads");
public static final CoinSide TAILS = new CoinSide("tails");
private final String name;
private CoinSide(String name) {
this.name = name;
}
public String toString() {
return name;
};
private static Random rnd = new Random();
public static CoinSide flip() {
return rnd.nextBoolean() ? HEADS : TAILS;
}
public static void main(String[] args) {
System.out.println(flip());
}
}
In release 5.0 and later releases, it is natural to write
CoinSide as an enum type:
public enum CoinSide {
HEADS, TAILS;
public String toString() {
return name().toLowerCase();
}
// flip and main same as in 1.4 implementation above
}
The lesson of this puzzle is: Upgrade
to the latest release of the Java platform. Newer releases include many
improvements that make life easier for programmers. You don't have to go out of
your way to take advantage of all the new features; some of them benefit you
with no effort on your part. For language and library designers, the lesson is:
Do not make programmers do things that the language or library could do for
them.
No comments:
Post a Comment
Your comments are welcome!