Wednesday, 19 September 2012

Puzzle 75: Heads or Tails?


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!