Thinking in Java(2-3) 邏輯、位元與位移運算子

Thinking in Java(2-3) 邏輯、位元與位移運算子
Photo by orbtal media / Unsplash

Java 的運算子是編寫高效程式的基石。本文將深入探討 Java 中的邏輯運算子、直接常數、位元運算子以及位移運算子,並透過實例來加深理解。

一、邏輯運算子(Logical Operators)

Java 中的邏輯運算子包括:

  • &&(AND)
  • ||(OR)
  • !(NOT)

這些運算子根據運算元的邏輯關係產生一個 boolean 值。

注意: 與 C/C++ 不同,Java 中不能直接將非 boolean 值用於邏輯表達式中,必須先使用關係運算子將其轉換為 boolean

常見錯誤示例

在 C++ 中,可以使用 while (node) 來替代 while (node != NULL),但在 Java 中,這樣的寫法會導致編譯錯誤。

// 常見錯誤
// C++ 中常使用 while (node) 簡寫 while (node != NULL)

// while (node) {} 在 Java中會報錯
while (node != null) {}

短路運算(Short-circuiting)

  • &&(AND):condition1 && condition2 中,如果 condition1false,那麼整個表達式必定為 false,因此不會計算 condition2
  • ||(OR):condition1 || condition2 中,如果 condition1true,那麼整個表達式必定為 true,因此不會計算 condition2

範例:

public class ShortCircuit {
  static boolean test1(int val) {
    print("test1(" + val + ")");
    print("result: " + (val < 1));
    return val < 1;
  }
  static boolean test2(int val) {
    print("test2(" + val + ")");
    print("result: " + (val < 2));
    return val < 2;
  }
  static boolean test3(int val) {
    print("test3(" + val + ")");
    print("result: " + (val < 3));
    return val < 3;
  }
  public static void main(String[] args) {
    boolean b = test1(0) && test2(2) && test3(2);
    print("expression is " + b);
  }
} /* Output:
test1(0)
result: true
test2(2)
result: false
expression is false
*/

在上述程式中,test2(2) 返回 false,使得整個表達式的結果為 false,因此 test3(2) 不會被執行。


二、直接常數(Literals)

直接常數是在程式中直接使用的數值,編譯器通常可以根據上下文推斷出常數的資料型別。在有歧義的情況下,需要開發者明確指定資料型別。

範例:

public class Literals {
  public static void main(String[] args) {
    int i1 = 0x2f; // Hexadecimal (lowercase)
    print("i1: " + Integer.toBinaryString(i1));
    int i2 = 0X2F; // Hexadecimal (uppercase)
    print("i2: " + Integer.toBinaryString(i2));
    int i3 = 0177; // Octal (leading zero)
    print("i3: " + Integer.toBinaryString(i3));
    char c = 0xffff; // max char hex value
    print("c: " + Integer.toBinaryString(c));
    byte b = 0x7f; // max byte hex value
    print("b: " + Integer.toBinaryString(b));
    short s = 0x7fff; // max short hex value
    print("s: " + Integer.toBinaryString(s));
    long n1 = 200L; // long suffix
    long n2 = 200l; // long suffix (but can be confusing)
    long n3 = 200;
    float f1 = 1;
    float f2 = 1F; // float suffix
    float f3 = 1f; // float suffix
    double d1 = 1d; // double suffix
    double d2 = 1D; // double suffix
    // (Hex and Octal also work with long)
  }
} /* Output:
i1: 101111
i2: 101111
i3: 1111111
c: 1111111111111111
b: 1111111
s: 111111111111111
*/

注意:

  • 超出型別範圍的值會導致編譯錯誤。
  • 若編譯器能夠自動識別型別,可以省略型別後綴。

指數表示法(Exponential Notation)

指數表示法用於表示非常大或非常小的數字,通常用於浮點數。

範例:

public class Exponents {
  public static void main(String[] args) {
    // Uppercase and lowercase 'e' are the same:
    float expFloat = 1.39e-43f;
    expFloat = 1.39E-43f;
    System.out.println(expFloat);
    double expDouble = 47e47d; // 'd' is optional
    double expDouble2 = 47e47; // Automatically double
    System.out.println(expDouble);
  }
} /* Output:
1.39E-43
4.7E48
*/

說明:

  • eE 表示乘以 10 的多少次方,例如 1.39e-43 表示 $1.39 \times 10^{-43}$。
  • 若未指定 fF,則編譯器默認為 double 型別。

三、位元運算子(Bitwise Operators)

Java 中的位元運算子主要用於對整數型別(如 intlong)的二進位位元進行操作:

  • XOR(異或):^
  • AND(與):&
  • OR(或):|
  • NOT(非):~

範例:

// XOR: ^
// AND: &
// OR:  |
// NOT: ~

// 定義變數
int a = 12;  // 二進制表達為 1100
int b = 6;   // 二進制表達為 0110

// XOR: ^ (異或)
int resultXor = a ^ b;  // 結果是 10 (二進制為 1010)

// AND: & (與)
int resultAnd = a & b;  // 結果是 4 (二進制為 0100)

// OR: | (或)
int resultOr = a | b;  // 結果是 14 (二進制為 1110)

// NOT: ~ (非)
int resultNot = ~a;  // 對 a 的每一位進行反轉

這些運算子也可以與指定運算子 = 結合使用,形成複合指定運算子,使程式更為簡潔:

a ^= b;  // a 現在等於 10
a |= b;  // a 現在等於 14
a &= b;  // a 現在等於 4
注意: ~ 是一元運算子,不能與指定運算子 = 結合使用。

四、位移運算子(Shift Operators)

Java 中的位移運算子用於對整數型別的數字進行二進位位元的移動:

  • 左移:<<——將位元向左移動,低位補 0
  • 右移:>>——將位元向右移動,對於正數,高位補 0;對於負數,高位補 1
  • 無符號右移:>>>——無論正負,高位都補 0

特點:

  • 只能作用於整數型別。
  • 可以與指定運算子結合使用,如 <<=>>=>>>=

位移操作可能導致的問題:

在執行位移操作時,byteshort 型別的數據會被自動提升為 int 型別,這可能會導致一些意外的結果。

範例:


public class URShift {
  public static void main(String[] args) {
    int i = -1;
    print(Integer.toBinaryString(i));
    i >>>= 10;
    print(Integer.toBinaryString(i));
    long l = -1;
    print(Long.toBinaryString(l));
    l >>>= 10;
    print(Long.toBinaryString(l));
    short s = -1;
    print(Integer.toBinaryString(s));
    s >>>= 10;
    print(Integer.toBinaryString(s));
    byte b = -1;
    print(Integer.toBinaryString(b));
    b >>>= 10;
    print(Integer.toBinaryString(b));
    b = -1;
    print(Integer.toBinaryString(b));
    print(Integer.toBinaryString(b>>>10));
  }
} /* Output:
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111
*/

解析:

  • 對於 int 型別,-1 的二進位表示為全 1
  • 使用無符號右移 >>> 後,高位補 0,結果位元數減少。
  • 對於 shortbyte,在位移操作時被提升為 int,可能導致預期外的結果。

五、結論

透過本文的介紹,我們深入了解了 Java 中的邏輯運算子、直接常數、位元運算子和位移運算子。理解這些運算子的用法和特性,能夠幫助我們在開發中編寫出更高效、更健壯的程式。