Java 運算子一次搞懂:從基本用法到別名陷阱的完整解析【Thinking in Java筆記(2-1)】

你知道a = b
在物件上不是「複製」而是「結拜」嗎?
你以為+
只是加法?其實它也能把字串和整數亂湊一通
基礎觀念:什麼是運算子?
Java 中的運算子是用來接收一個或多個值(也稱為操作數,Operands)然後算出結果的符號。像是我們熟悉的 +
、-
、*
、/
、=
都是常見的運算子
不過有些運算子會偷偷改變變數本身的值,像是這些:
++
(自增)--
(自減)=
(賦值)
來個例子讓你印象深刻:
int x = 5;
x++; // x 現在是 6
運算子 vs 方法
你可以用 x + y
,也可以用 add(x, y)
。功能一樣,但寫法不同。運算子比較短,方法比較明白
int sum = x + y; // 運算子寫法
int sum = add(x, y); // 方法寫法
兩種寫法沒有差別,只是風格不同而已
常見 Java 運算子分類快速表
類型 | 例子 | 說明 |
---|---|---|
算術運算子 | + - * / % |
基本數學運算 |
賦值運算子 | = |
將值給變數 |
比較運算子 | == != > < >= <= |
判斷條件 |
邏輯運算子 | `&& | |
位元運算子 | `& | ^ ~ << >>` |
字串運算子 | + += |
字串拼接超好用 |
在 Java 裡,只有 +
可以拼字串,其他都是給數字玩的
String message = "Hello, " + 42; // 會變成 "Hello, 42"
提示: 當+
運算子用於字串和非字串類型的組合時,Java 編譯器會自動將非字串元素轉換為String
。這使得連接String
和其他數據類型變得非常方便
運算子優先順序
Java 跟數學一樣有「先乘除後加減」的規則。如果你不加括號,它就照自己的邏輯來算
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
*/
賦值(Assignment)與物件引用的陷阱:你以為在複製,其實只是搬過去一起住
什麼是「左值」和「右值」?
在 Java 中,賦值語句長這樣:
a = b
這裡的 a
稱為「左值」(L-value),它代表的是一個可以儲存資料的變數位置。而 b
是「右值」(R-value),它是一個可以產生資料內容的東西,可以是變數、常數或表達式
int a;
a = 4; // 合法
4 = a; // 不合法,因為 4 不是變量,無法存儲值
基本類型資料的賦值行為:純複製,好懂又安全
Java 的基本類型(primitive types)像是 int、double、char 等,是直接複製「值」,彼此互不干涉:
int a = 10;
int b = a;
b = 20;
System.out.println(a); // a 仍然是 10
這種行為很單純,左值拿到的是右值的內容副本
物件的賦值行為:別名現象(Aliasing)
物件類型的賦值不會複製內容,而是複製引用(Reference)
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
*/
t1 = t2
把t2
的參考位置給了t1
,兩個變數現在都指向同一個物件- 改 t1.level 就像改 t2.level,因為根本就是同一間房子
如果你想真的「複製」一個物件,要自己 new 一個新物件,或用 clone、copy constructor
方法傳參也是引用複製:參數看起來像影分身,實際上是一條繩子綁在同一個物件上
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
*/
- 雖然
l
是x
的一個副本,但它們都指向同一個 Letter 物件 - 所以在
change
方法中改l.c
,實際上也是在改x.c
總結 alias 怎麼辦?
- 想要複製內容,就自己 new 一個物件來放。
- 別把「複製參考」當作「複製物件」。
- 不確定是不是 alias 時,就不要改物件屬性。
寫程式的時候,最怕不是 bug,而是你根本不知道你在改哪一個記憶體位置
總結
本文深入探討了 Java 中運算子的基本概念、優先級以及賦值行為。你應該已經了解了:
- 運算子如何與操作數交互並生成新值
- 優先順序怎麼影響你的表達式結果
- 基本類型 vs 物件類型的賦值差異
- 別名現象與傳參時的物件共享問題
理解這些概念對於編寫高效且無錯的 Java 程式至關重要。你不需要死背,但至少別再對著 t1 = t2
感到疑惑
Comments ()