嗨,本篇文章來講說 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 元 )。