JAVA更多的類謎題75:頭還是尾

字號:

這個程序的行為在1.4版和5.0版的Java平臺上會有些變化。這個程序在這些版本上會分別做些什么呢?(如果你只能訪問5.0版本的平臺,那么你可以在編譯的時候使用-source 1.4標記,以此來模擬1.4版的行為。)
    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";
     }
    }
    該程序看起來根本沒有使用5.0版的任何新特性,因此很難看出來為什么它們在行為上應該有差異。事實上,該程序在1.4或更早版本的平臺上是不能編譯的:
    CoinSide.java:7:
    incompatible types for ?: neither is a subtype of the other
    second operand: Heads
    third operand : Tails
     return rnd.nextBoolean() ?
     ^
    條件操作符(?:)的行為在5.0版本之前是非常受限的[JLS2 15.25]。當?shù)诙€和第三個操作數(shù)是引用類型時,條件操作符要求它們其中的一個必須是另一個的子類型。Heads和Tails彼此都不是對方的子類型,所以這里就產生了一個錯誤。為了讓這段代碼能夠編譯,你可以將其中一個操作數(shù)轉型為二者的公共超類:
    return rnd.nextBooleam() ?
    (CoinSide)Heads.INSTANCE : Tails.INSTANCE;
    在5.0或更新的版本中,Java語言顯得更加寬大了,條件操作符在第二個和第三個操作數(shù)是引用類型時總是合法的。其結果類型是這兩種類型的最小公共超類。公共超類總是存在的,因為Object是每一個對象類型的超類型。在實際使用中,這種變化的主要結果就是條件操作符做正確的事情的情況更多了,而給出編譯期錯誤的情況更少了。對于我們當中的語言菜鳥來說,作用于引用類型的條件操作符的結果所具備的編譯期類型與在第二個和第三個操作數(shù)上調用下面的方法的結果相同:
     T choose(T a,T b) { }
    本謎題所展示的問題在1.4和更早的版本中發(fā)生得相當頻繁,迫使你必須插入只是為了遮掩你的代碼的真實目的而進行的轉型。這就是說,該謎題本身是人為制造的。在5.0版本之前,使用類型安全的枚舉模式來編寫CoinSide對程序員來說會顯得更自然一些[EJ Item 21]:
    下面的程序全部是由同步化(synchronized)的靜態(tài)方法組成的。那么它會打印出什么呢?在你每次運行這段程序的時候,它都能保證會打印出相同的內容嗎?
    public class PingPong{
     public static synchronized void main(String[] a){
     Thread t = new Thread(){
     public void run(){ pong(); }
     };
     t.run();
     System.out.print( "Ping" );
     }
     static synchronized void pong(){
     System.out.print( "Pong" );
     }
    }
    在多線程程序中,通常正確的觀點是程序每次運行的結果都有可能發(fā)生變化,但是上面這段程序總是打印出相同的內容。在一個同步化的靜態(tài)方法執(zhí)行之前,它會獲取與它的Class對象相關聯(lián)的一個管程(monitor)鎖[JLS 8.4.3.6]。所以在上面的程序中,主線程會在創(chuàng)建第二個線程之前獲得與PingPong.class相關聯(lián)的那個鎖。只要主線程占有著這個鎖,第二個線程就不可能執(zhí)行同步化的靜態(tài)方法。具體地講,在main方法打印了Ping并且執(zhí)行結束之后,第二個線程才能執(zhí)行pong方法。只有當主線程放棄那個鎖的時候,第二個線程才被允許獲得這個鎖并且打印Pong 。根據(jù)以上的分析,我們似乎可以確信這個程序應該總是打印PingPong。但是這里有一個小問題:當你嘗試著運行這個程序的時候,你會發(fā)現(xiàn)它總是會打印PongPing。到底發(fā)生了什么呢?