Thinking in Java(2-1) 運算子入門:深入理解與應用

Thinking in Java(2-1) 運算子入門:深入理解與應用
Photo by orbtal media / Unsplash

Java 作為一門強大的編程語言,其運算子(Operators)在日常編程中扮演著至關重要的角色。本文將帶領讀者深入了解 Java 中的各種運算子,從基本概念到賦值行為,並通過實例解析,讓您對 Java 運算子有一個全面而系統的認識。

一、運算子基礎

1. 運算子的作用

在 Java 中,運算子接受一個或多個參數(也稱為操作數,Operands),並生成一個新值。有些運算子還會直接改變操作數本身的值,例如:

  • 自增運算++
  • 自減運算--
  • 賦值運算=

這些運算子在操作過程中,會直接修改操作數的值。

2. 運算子與方法的比較

雖然運算子的參數形式與普通的方法調用不同,但實際上效果相同。例如:

int sum = x + y; // 使用運算子
int sum = add(x, y); // 使用方法

3. 常見運算子

Java 中的 +-*/= 等常見運算子與其他編程語言(如 C++、Python)類似。

4. 運算子的適用類型

大多數運算子(如算術運算子)僅適用於基本類型(Primitives),如 intfloat 等。但也有例外:

  • 賦值運算=
  • 等於運算==
  • 不等於運算!=

這些運算子可以操作所有物件。

5. 特殊的字串運算符

在 Java 中,String 類別特別支持 ++= 運算子,用於連接字串。例如:

String greeting = "Hello, " + "World!";
提示:+ 運算子用於字串和非字串類型的組合時,Java 編譯器會自動將非字串元素轉換為 String。這使得連接 String 和其他數據類型變得非常方便。

二、運算子優先級(Precedence)

1. 基本原則

Java 中,運算子的優先級遵循一般數學中的規則:

  1. 先乘除,後加減。
  2. 括號內的運算優先進行。

2. 使用括號控制運算順序

為了精確控制運算順序,可以使用括號。例如:

public class Precedence {
  public static void main(String[] args) {
    int x = 1, y = 2, z = 3;
    int a = x + y - 2/2 + z; // 計算順序:2/2 -> x + y -> 結果 - 1 -> 加上 z
    int b = x + (y - 2)/(2 + z); // 括號內優先:y - 2 和 2 + z,然後計算除法,最後加上 x
    System.out.println("a = " + a + " b = " + b);
  }
}
/* 輸出:
a = 5 b = 1
*/

在以上代碼中:

  • a 的計算過程:
    1. 2/2 計算結果為 1
    2. 然後按照加減法從左到右計算:1 + 2 - 1 + 3 = 5
  • b 的計算過程:
    1. 括號內先計算:(2 - 2) = 0(2 + 3) = 5
    2. 然後進行除法:0 / 5 = 0
    3. 最後加上 x 的值:1 + 0 = 1

三、賦值(Assignment)

1. 基本概念

  • 賦值運算=:將等號右邊的值複製給左邊的變量。
  • 右值(R-value):可以是常數、變量或表達式。
  • 左值(L-value):必須是一個已命名的變量,有實際的存儲空間來保存值。

例如:

int a;
a = 4; // 合法
4 = a; // 不合法,因為 4 不是變量,無法存儲值

2. 物件的賦值行為

當物件進行賦值時,實際上是複製了物件的引用(Reference),而不是物件本身。因此,兩個引用將指向同一個物件,這被稱為別名現象(Aliasing)

示例解析

class Tank {
  int level;
}

public class Assignment {
  public static void main(String[] args) {
    Tank t1 = new Tank();
    Tank t2 = new Tank();
    t1.level = 9;
    t2.level = 47;
    System.out.println("1: t1.level: " + t1.level + ", t2.level: " + t2.level);
    t1 = t2;
    System.out.println("2: t1.level: " + t1.level + ", t2.level: " + t2.level);
    t1.level = 27;
    System.out.println("3: t1.level: " + t1.level + ", t2.level: " + t2.level);
  }
}
/* 輸出:
1: t1.level: 9, t2.level: 47
2: t1.level: 47, t2.level: 47
3: t1.level: 27, t2.level: 27
*/

解析:

  1. 初始狀態:
    • t1.level = 9
    • t2.level = 47
  2. 執行 t1 = t2; 後:
    • t1t2 指向同一個 Tank 物件(原先 t1 指向的物件將被垃圾回收)。
    • 因此,t1.levelt2.level 都為 47
  3. 修改 t1.level = 27; 後:
    • 由於 t1t2 指向同一個物件,所以兩者的 level 都變為 27

避免別名現象的方法:

如果希望複製物件的值,而不是引用,可以在賦值時創建一個新的物件,或實現物件的複製方法。

3. 方法調用中的別名問題

在方法調用時,傳遞的物件參數實際上也是引用,這可能導致在方法內對參數的修改會影響到原始物件。

示例解析

class Letter {
  char c;
}

public class PassObject {
  static void f(Letter y) {
    y.c = 'z';
  }
  public static void main(String[] args) {
    Letter x = new Letter();
    x.c = 'a';
    System.out.println("1: x.c: " + x.c);
    f(x);
    System.out.println("2: x.c: " + x.c);
  }
}
/* 輸出:
1: x.c: a
2: x.c: z
*/

解析:

  • 在主方法中,創建了物件 x,並將其屬性 c 賦值為 'a'
  • 調用方法 f(x);,傳遞了物件 x 的引用。
  • 在方法 f 中,修改了參數 y 的屬性 c'z'
  • 由於 yx 引用同一個物件,因此 x.c 的值也被改變。
注意: 在 Java 中,方法調用時傳遞的是物件引用的副本,但引用指向的物件是同一個。因此,對參數物件的修改會影響到原始物件。

四、總結

本文深入探討了 Java 中運算子的基本概念、優先級以及賦值行為。通過實例,我們了解了:

  • 運算子如何與操作數交互並生成新值。
  • 運算子的優先級如何影響表達式的計算順序。
  • 賦值運算子在基本類型和物件上的不同行為。
  • 別名現象以及在方法調用中物件引用的傳遞方式。

理解這些概念對於編寫高效且無錯的 Java 程式至關重要。希望本文能夠幫助您在學習 Java 的道路上更進一步。