爲何要將局部變量的做用域最小化?

嗨,本篇文章來講說 Java 的一個小細節:爲何要將局部變量的做用域最小化?java

明人不說暗話啊。這篇文章的靈感來源於《Effective Java》,這本書我買了有好長好長一段時間了,書頁都已經泛黃,烙下了時間的痕跡,但我仍然尚未把這本書讀完。說來慚愧啊。bash

爲何呢?總感受這本書的中文翻譯有點拙劣,讀起來煩悶枯燥。明明感受做者說得很是有道理,但就是提不起半點興致。ui

(說完這句話,總以爲有點對不住這本書的譯者,畢竟吐槽容易,分享難啊。)spa

爲何要說這些廢話呢,由於怕你們以爲這是不值一提的細節,但每每細節決定成敗啊。你們不妨換一種比較輕鬆的心態來讀一讀。反正我是不怎麼喜歡高談闊論的文章,讀完後每每只能感慨一句:「說得不錯啊」,但也僅此而已。翻譯

好了,來步入正題。code

String [] strs = {"洛陽","牡丹","甲天下"};
List<String> list = Arrays.asList(strs);

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = (String) iterator.next();
    System.out.println(s);
}

list.add("沉默王二");
Iterator<String> iterator1 = list.iterator();
while (iterator.hasNext()) {
    String s = (String) iterator1.next();
    System.out.println(s);
}
複製代碼

你們用「肉眼」看完上面這段代碼後,會以爲有問題嗎?cdn

若是不細心的話,好像真的很難發現「複製-粘貼」引起的這個問題:第二個 while 循環的條件中使用了以前的變量 iterator,而不是它應該使用的 iterator1(粘貼後遺漏了變量的修改)。這個問題將會致使代碼在運行的時候拋出 java.lang.UnsupportedOperationException 的錯誤。作用域

說句實在話,在敲代碼的這十年來,沒少複製粘貼,沒少由於粘貼後變量沒有修改完全,而致使出現了各類意料以外的 bug。get

假如把變量的做用域最小化的話,還真的可以減小這種由於「複製-粘貼」而致使出現的錯誤。好比說把 while 循環改形成 for 循環。string

for (Iterator<String> iterator = list.iterator();iterator.hasNext();) {
    String s = (String) iterator.next();
    System.out.println(s);
}

list.add("沉默王二");
for (Iterator<String> iterator = list.iterator();iterator.hasNext();) {
    String s = (String) iterator.next();
    System.out.println(s);
}
複製代碼

第二個 for 循環使用了和第一個 for 循環如出一轍的代碼,連 iterator 這個變量也不須要修改了。

從另外一方面來看的話,for 循環比 while 循環更簡短,可讀性更好。for 循環還有另一種最經常使用的寫法,示例以下。

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
複製代碼

但這種寫法仍有改進的地方,由於從字節碼的角度來看,每次循環都要調用一次 size() 方法。

2: iload_1
3: aload_0
4: getfield      #4 // Field list:Ljava/util/List;
7: invokeinterface #5, 1 // InterfaceMethod java/util/List.size:()I
12: if_icmpge     40
15: getstatic     #6 // Field java/lang/System.out:Ljava/io/PrintStream;
18: aload_0
19: getfield      #4 // Field list:Ljava/util/List;
22: iload_1
23: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
28: checkcast     #8 // class java/lang/String
31: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: iinc          1, 1
37: goto          2
40: return
複製代碼

size() 方法雖然簡短,但也有消耗啊。都有什麼消耗呢?說幾個專業名詞你們感覺一下,好比說:建立棧幀、調用方法時保護現場、調用方法完畢後恢復現場。

(允許我尷尬一下,在寫這篇文章以前,我一直用的上面這種 for 循環格式。看來寫文章仍是可以督促本身進步啊。)

怎麼改進呢,看下面這種寫法(強烈推薦啊)。

for (int i = 0, n = list.size(); i < n; i++) {
    System.out.println(list.get(i));
}
複製代碼

在 for 循環內部聲明兩個變量:i 和 n,n 用來保存 i 的極限值,這樣就減小了 size() 方法的調用次數(僅有一次了)。

再來看一段代碼。

String pre_name = "沉默";
String last_name = "王二";

System.out.println(pre_name);
System.out.println(last_name);
複製代碼

上面這段代碼看起來挺規整的,沒什麼問題,對吧?它沒有遵照約定——將局部變量的做用域最小化。

pre_name 變量的做用域結束的有點晚;last_name 變量的做用域開始的有點早。假如第一個 System.out.println() 出錯的話,last_name 的聲明就變得毫無心義了。

(這只是一個例子,變量的處理方法可能比 System.out.println() 複雜得多。)

好的寫法應該是下面這樣子。

String pre_name = "沉默";
System.out.println(pre_name);

String last_name = "王二";
System.out.println(last_name);
複製代碼

有人可能以爲這不是在吹毛求疵嗎?真不是的,變量就應該是在第一次使用它的時候聲明。不然的話,變量的做用域要麼開始的太早,要麼結束的太晚。

好了,這篇文章到此就結束了,很是的簡短,但講清楚了「爲何要將局部變量的做用域最小化」。

PS:噓,告訴你們一個好消息啊。關注「沉默王二」公衆號,後臺回覆關鍵字「666」便可獲取《Effective Java 中文第三版》(書本訂價 119 元 )。

相關文章
相關標籤/搜索