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

字號(hào):

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