|
|
|
public class Expressive { |
|
|
|
public static void main(String args[]) { |
|
Expressive puzzler = new Expressive(); |
|
puzzler.Oddity(); |
|
puzzler.Chage(); |
|
puzzler.LongDivision(); |
|
puzzler.Elementary(); |
|
puzzler.JoyOfHex(); |
|
puzzler.Multicast(); |
|
puzzler.CleverSwap(); |
|
puzzler.DosEquis(); |
|
puzzler.Tweedledum(); |
|
puzzler.Tweedledee(); |
|
} |
|
|
|
/* |
|
* Puzzler-1: Oddity |
|
* isOdd 是判斷奇數的方法, Oddity 為測試碼。 |
|
*/ |
|
private void Oddity() { |
|
System.out.println("Oddity Result:"); |
|
for(int i = -3; i <= 3; i++) { |
|
if( isOdd(i) == true ) { |
|
System.out.println( i + " is an odd."); |
|
} else { |
|
System.out.println( i + " isn't an odd."); |
|
} |
|
} |
|
System.out.println(""); |
|
} |
|
|
|
public static boolean isOdd(int i) { |
|
return i % 2 == 1; // 此行無法對負數做正確的判斷 |
|
|
|
// 底下三行皆可正確判斷,但用 & 運算子速度較快 |
|
// return i % 2 != 0; |
|
// return ((i & 1) == 1); |
|
// return ((i & 1) != 0); |
|
} |
|
|
|
/* |
|
* Puzzler-2: Change |
|
* 不是所有的數值都能用二進位表示。 |
|
* Java對十進位小數都是以double為預設型態。 |
|
*/ |
|
private void Chage() { |
|
System.out.println("Change Result:"); |
|
System.out.println(2.00 - 1.10); // 此行無法正確輸出 0.9 |
|
System.out.println(""); |
|
|
|
/* |
|
* 解法一: |
|
* System.out.printf("%.2f\n", 2.00 - 1.10); |
|
*/ |
|
|
|
/* |
|
* 解法二: |
|
* System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10"))); |
|
*/ |
|
|
|
/* |
|
* 當需要正確數值結果時,請使用 int、long 型態或BigDecimal類別。 |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-3: LongDivision |
|
*/ |
|
private void LongDivision() { |
|
System.out.println("LongDivision Result:"); |
|
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000; // 這行會溢位(overflow) |
|
final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; |
|
|
|
System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY); |
|
System.out.println(""); |
|
|
|
/* |
|
* |
|
* 在 Java 中整數運算預設是以 int 型態處理(32位元),最大值為 2147483647。 |
|
* 而 86400000000 大於 2147483647,所以就溢位了,結果會只取最右邊的32位元。 |
|
* 1010000011101110101110110000000000000 |
|
* 11101110101110110000000000000 ==> 取這部分,值為500654080。 |
|
* |
|
* 解法:在整數後頭加上L |
|
* final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000; |
|
* final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000; |
|
* |
|
* 在處理大數值時,請小心溢位。 |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-4: Elementary |
|
*/ |
|
private void Elementary() { |
|
System.out.println("Elementary Result:"); |
|
System.out.println(12345 + 5432l); |
|
System.out.println(""); |
|
/* |
|
* 注意l是小寫的L,不是 1。請用L來表示 long 型態的數值。 |
|
* System.out.println(12345 + 5432L); |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-5: JoyOfHex |
|
*/ |
|
private void JoyOfHex() { |
|
System.out.println("JoyOfHex Result:"); |
|
System.out.println( |
|
Long.toHexString(0x100000000L + 0xcafebabe)); |
|
System.out.println(""); |
|
/* |
|
* 十進位數值要表示負數的話,必須在數字最左邊加上負號,但對8進位與16進位而言,只要最左邊的符號位元(最高位元)是1就行。 |
|
* 所以0xcafebabe為十進位的-889275714,此題還是個混合型態的運算:long + int, |
|
* 結果0xcafebabe擴展成了0xffffffffcafebabe,所以結果如下: |
|
* 0xFFFFFFFFCAFEBABEL |
|
* + 0x0000000100000000L |
|
* = 0x00000000CAFEBABEL |
|
* |
|
* 解法: |
|
* System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL)); |
|
* |
|
* 盡量少用混合型態的運算。 |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-6: Multicast |
|
*/ |
|
private void Multicast() { |
|
System.out.println("Multicast Result:"); |
|
System.out.println((int) (char) (byte) -1); // 結果為 65535 |
|
System.out.println(""); |
|
|
|
/* |
|
* 除了型態轉換外,也得考慮是否有符號擴展,型態為 char 時,使用零擴展。 |
|
* 轉換過程如下: |
|
* 一、-1 從int --> byte,值仍為-1,但只剩8位元:11111111。 |
|
* |
|
* 二、byte --> char,因為byte是signed,而char是unsigned,所以byte轉為int再轉為char: |
|
* byte -> int: 11111111(8)-> 11111111111111111111111111111111(32) |
|
* int -> char: 11111111111111111111111111111111(32)-> 1111111111111111(16) |
|
* |
|
* 三、char --> int, 因為char是unsigned,直接補零: |
|
* 1111111111111111(16) -> 00000000000000001111111111111111(32) |
|
* 這個00000000000000001111111111111111的值就是2^16 - 1 = 65535。 |
|
* |
|
* 想要把byte轉成char,可以使用遮罩: |
|
* char c = (char) (b & 0xff) |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-7: CleverSwap |
|
*/ |
|
private void CleverSwap() { |
|
System.out.println("CleverSwap Result:"); |
|
int x = 1984; |
|
int y = 2001; |
|
x ^= y ^= x ^= y; |
|
System.out.println("x = " + x + "; y = " + y); // 結果 x = 0; y = 1984 |
|
System.out.println(""); |
|
|
|
/* |
|
* Java語言規格書提到:「運算子和運算元在計算時,順序是從左到右的。」 |
|
* 所以在計算 x ^= expr時,x得值已經被取出來的, |
|
* 然後x在和expr的結果做XOR運算,最後再將XOR運算結果指定給x。 |
|
* |
|
* Java 對x ^= y ^= x ^= y 做了底下的事: |
|
* int tmp1 = x; |
|
* int tmp2 = y; |
|
* int tmp3 = x ^ y; |
|
* x = tmp3; ==> 最右邊的指定運算 |
|
* y = tmp2 ^ tmp3; ==> 中間的指定運算 |
|
* x = tmp1 ^ y; ==> 最左邊的指定運算 |
|
* |
|
* 交換兩個變數的值可用 |
|
* x = x ^ y; |
|
* y = y ^ x; |
|
* x = y ^ x; |
|
* |
|
* 或是醜醜的 y = (x ^= (y ^= x)) ^ y; |
|
* |
|
* 在同一個運算式中,同一個變數最好不要被指定超過一次。 |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-8: DosEquis |
|
*/ |
|
private void DosEquis() { |
|
System.out.println("DosEquis Result:"); |
|
char x = 'X'; |
|
int i = 0; |
|
System.out.print(true ? x : 0); |
|
System.out.print(false ? i : x); |
|
System.out.println(""); // 結果 X88 |
|
System.out.println(""); |
|
|
|
/* |
|
* 這題的規則還真多,請參考 |
|
* http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25 |
|
* |
|
* 請不要用混合型態。 |
|
* |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-9: Tweedledum |
|
*/ |
|
private void Tweedledum() { |
|
short x = 0; |
|
int i = 123456; |
|
|
|
x += i; // Must be LEGAL,有隱喻的轉型 |
|
// x = x + i; // Must be ILLEGAL |
|
|
|
System.out.println("Tweedledum result:"); |
|
System.out.println("x:" + x + ", i:" + i); // 結果 x:-7616, i:123456 |
|
System.out.println(""); |
|
|
|
/* |
|
* 複合指定運算式會自動將運算的結果轉成和左邊運算子一樣的資料型態。 |
|
*/ |
|
} |
|
|
|
/* |
|
* Puzzler-10: Tweedledee |
|
*/ |
|
private void Tweedledee() { |
|
Object x = "Buy "; |
|
String i = "Effective Java!"; |
|
|
|
x = x + i; // Must be LEGAL |
|
x += i; // Must be ILLEGAL |
|
|
|
System.out.println("Tweedledee result:"); |
|
System.out.println("x:" + x + ", i:" + i); |
|
System.out.println(""); |
|
|
|
/* |
|
* 這題跟 JDK 版本有關,有興趣的人可看 stackoverflow 上的討論 |
|
* http://stackoverflow.com/questions/14218385/how-do-i-make-e1-e2-illegal-while-e1-e1-e2-is-legal |
|
*/ |
|
} |
|
|
|
/* |
|
* 在下的心得:寫程式幹嘛用有陷阱的語法阿。 |
|
*/ |
|
} |