1. 程式人生 > 其它 >淺談Java中短路運算子與邏輯運算子

淺談Java中短路運算子與邏輯運算子

技術標籤:Java學習面試題java面試

1、邏輯運算子(部分)

符號名稱
&&短路與運算子
||短路或運算子
&與運算子
|或運算子

對於理工科學習者來說,邏輯運算是較為基礎的概念,通常會在大一的離散數學課程中有所瞭解。在Java以及更多C-Like語言中,&|會分別表示邏輯運算中的與、或,他們的運算結果與我們在數學書中所學的邏輯運算規則並無差異。但是,在實際程式設計的過程中,我們反而會更多使用&&||,甚至不少同學都不瞭解&|兩個運算子。那麼,這究竟是為什麼呢?

2、短路運算子

讓我們來回到最初學習邏輯運算時解決問題的真值表,以“或運算”為例:

ab結果
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

a和b中,只要至少有一個為true,最終輸出的結果則為true。那麼,從演算法優化的角度來思考問題的話,我們為了經過最少步驟還能輸出可靠的結果,我們便可以把“或運算”定義為,有一個true,就輸出true。

由此,“或運算”可以被優化為:從左向右,遇到有一個布林表示式為true,則返回true,不進行之後的運算。

與之相似的,“與運算”可以被優化為:從左向右,遇到有一個布林表示式為false,則返回false,不進行之後的運算。

故此,短路運算子被設計了出來。但為了兼顧“執行命令並返回”、“純粹的數學計算”等多種應用場景,傳統的非短路邏輯運算子也沒有被短路邏輯運算子而取代。

此外,對於連寫的短路運算子,如func1()||func2()||func3()||func4(),編譯器也會為此優化,我們不妨來閱讀這一部分的位元組碼來驗證這個結論:

public static void main(String[] args) {
        boolean b1 = func1() || func2() || func3() || func4();
        System.out.println("------------------");
        boolean b2 = func1() | func2() | func3() |
func4(); } // func1() - func4() here

短路“或”的位元組碼如下:

0: invokestatic  #7                  // Method func1:()Z
3: ifne          24
6: invokestatic  #13                 // Method func2:()Z
9: ifne          24
12: invokestatic  #16                 // Method func3:()Z
15: ifne          24
18: invokestatic  #19                 // Method func4:()Z
21: ifeq          28
24: iconst_1
25: goto          29
28: iconst_0
29: istore_1

3行、9行、15行的ifne是將棧頂元素與0(false)相比,如果不為false則跳轉到24行將常量1(true)入棧,完成賦值,會跳過其餘的執行。直到最後21行,才將最後方法結果的值再與0相比,如果還是0,則將常量0入棧,完成賦值。

普通“或”等位元組碼如下:

38: invokestatic  #7                  // Method func1:()Z
41: invokestatic  #13                 // Method func2:()Z
44: ior
45: invokestatic  #16                 // Method func3:()Z
48: ior
49: invokestatic  #19                 // Method func4:()Z
52: ior
53: istore_2

則是普通的或運算,無跳轉,順序執行最後賦值。

3、應用與陷阱

在最起初,筆者重新認識短路運算子是在這樣一段程式碼中:

public LoginCheckDTO XxxLoginCheck(String password) {
	//some codes
	if ( password == null || password.length() == 0 ) {
		return LoginCheckDTO.EMPTY_PASSWORD;
	}
	//some codes
}

當時筆者認為,如果passwordnull,在嘗試呼叫password.length()時,會丟擲空指標異常,故此寫法不好。但在後來進行測試的時候,發現這樣寫並沒有問題,查閱相關資料便了解了短路運算子的概念。

在這個例子中,當執行password == null返回true的時候,隨後的表示式將不會被執行,就不存在丟擲異常的情況了。這便是短路運算子較為常用的一個應用場景。

除此之外,我們還要警惕短路運算子導致的指令執行不完整。

譬如如下應用場景,我們希望利用條件語句來判斷所有燈在上一狀態是否都開著,並且無論如何我們希望最後開啟所有的燈。但是我們錯誤使用了短路運算子:

public boolean checkAndTurnOnAll() {
	return checkAndTurnOn1() && checkAndTurnOn2();
}

private boolean checkAndTurnOn1() {
	boolean check = check1();
	turnOn1();
	return check;
}

private boolean checkAndTurnOn2() {
	boolean check = check2();
	turnOn2();
	return check;
}

在這個場景中,如果第一盞燈在上一個狀態是關閉狀態,在checkAndTurnOn1()中雖然會執行turnOn1(),並且返回false,但由於短路特性checkAndTurnOn2()並不會被執行,所以最後期望的看到所有的燈都被開啟不一定會實現。這種情況應當使用&