1. 程式人生 > >Java謎題暢讀版之表示式謎題

Java謎題暢讀版之表示式謎題

謎題1:奇數性

下面判斷一個數是否是奇數,這個程式對嗎?
public static boolean isOdd(int i){
    return i % 2 == 1;
}
這裡需要注意的是負奇數,負奇數i%2 = -1;
正確的程式可以如下:
public static boolean isOdd(int i){
    return i % 2 != 0;
}
或者有效能更好的寫法:
public static boolean isOdd(int i){
    return (i & 1) != 0;
}
這個怎麼去理解它呢?1的二進位制000...001, 任何奇數的二進位制尾數都是1,任何偶數的二進位制尾數都是0;所以奇數i&1的結果!=0;

謎題2:找零時刻

Tom去汽配店買一個1.10元的火花塞,他給店主2元,請問找零多少?
public class Change{
    public static void main(String args[]){
        System.out.println(2.00 - 1.10);
    }
}
不是你期望的0.9, 而是0.8999999999999.
問題的根本原因在於二進位制不能精確地表示浮點數,可以用BigDecimal來修正一下:
import java.math.BigDecimal;
public class Change1{
    public static void main(String args[]){
        //此程式我還發現BigDecimal有一個有用的地方,即可以控制列印精度。試著把2.00改成2.0000;
        System.out.println(new BigDecimal("2.00").
        subtract(new BigDecimal("1.10")));
    }
}
但是由於Java沒有在語言級別上提供BigDecimal的支援,所以計算是比較慢的。總之,在精確計算的地方避免使用float和double。對於貨幣的計算,應使用int,long和BigDecimal。

謎題3:長整除

看一眼下面的程式,然後說出結果:
public class LongDivision{
    public static void main(String args[]){
        final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
        System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);
    }
}
上下兩個乘法計算表示式對的很整齊,而且使用了long,不會溢位。你幾乎都忍不住想說1000,但是你錯了。
問題的原因在於參與乘法運算的數字都是int,運算的結果也是int,int就真的會溢位了。

謎題4:初級問題

下面程式的結果是什麼?
public class Elementary{
    public static void main(String[] args){
        System.out.println(12345+5432l);
    }
}
66666?不對, 是17777。
問題在於5432l,最後的l是L的小寫。這個問題很幼稚,但它給了一個深刻的教訓,當表示long的時候,一定要用L。同樣要避免用L小寫l做變數名:
List l = new ArrayList<String>();
l.add("Foo");
System.out.println(1);

謎題5:十六進位制的趣事

下面程式的結果是什麼?
public class JoyOfHex{
    public static void main(String[] args){
        System.out.println(
            Long.toHexString(0x100000000L + 0xcafebabe));
    }
}
你可能期待他打印出1cafebabe, 實際上結果卻是cafebabe。
問題出在0xcafebabe。我們常見的十進位制的字面量可以在前面加符號來表明它是負的。而16進位制或8進位制,只能看最後二進位制形式的最高位了。而0xcafebabe是一個負數。具體的計算過程我就不說了。

謎題6:多重轉型

下面多重轉型的結果是什麼?
public class Multicast{
    public static void main (String[] args){
        System.out.println((int)(char)(byte) -1);
    }
}
是65535.
為什麼不是-1呢?從較寬整型變成較窄整型,直接砍掉多餘的高位;從較窄整型轉較寬整型有一點麻煩,不過有一條很簡單的規則能夠描述從較窄的整型轉換成較寬的整型時的符號擴充套件行為:如果最初的數值型別是有符號的,那麼就執行符號擴充套件;如果它是char,那麼不管它將要被轉換成什麼型別,都執行零擴充套件。
我們來看這個題,最開始是一個int的-1,它的二進位制所有位都是1,它是由符號的,轉byte的時候,砍掉高位之後,留下的8位都是1,所以結果仍然是-1,byte轉char,執行符號擴充套件,即把高位用符號位補齊,這裡是用1填補高8位;char轉int,執行0拓展,即高位補0. 結果變成正的,即2^16-1, 65535.

謎題7:互換內容

下面的程式期望通過一系列的異或來交換兩個數。
public class CleverSwap{
    public static void main(String[] args){
        int x = 1984; // (0x7c0)
        int y = 2001; // (0x7d1)
        x^= y^= x^= y;
        System.out.println("x= " + x + "; y= " + y);
    }
}
異或的運算大家不熟悉,我只想說一點,不通過中間變數交換兩個數的正確做法是:
x^=y; y^=x; x^=y;

謎題8:Dos Equis

public class DosEquis{
    public static void main(String[] args){
        char x = 'X';
        int i = 0;
        System.out.println(true ? x : 0);
        System.out.println(false ? i : x);
    }
}
你心裡可能有一個結果,但是估計自己都覺得不靠譜,結果是X88。你知道不靠譜的原因嗎?在於條件表示式返回的結果型別的確定,條件表示式返回結果的型別確定有點複雜,我懶得重複,想強調一點就是,條件表示式的第二第三的運算元最好使用同一種類型。

謎題9:半斤

下面的兩種寫法有什麼不一樣嗎? 請給出x,i的定義,使第一個表示式合法,第二個表示式不合法。
x += i;
x = x+1;
問題的關鍵在於+=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、^=和|=這類複合賦值運算,它含有自動轉型。
比如我有:short x = 1; int i = 123456;第一種寫法,編譯器不會有任何意見(當然結果也會出你意料之外),第二種寫法編譯器會說不能從int直接轉short;

謎題10:八兩

和上面的相反,請給出定義,使第一個合法,第二個不合法。
x = x + i;
x += i;

Object x = "s"; String i = "i";
複合賦值只能針對簡單型別和String。