嗨,本篇文章來說說 Java 的一個小細節(jié):為什么要將局部變量的作用域最小化?
明人不說暗話啊。這篇文章的靈感來源于《Effective
Java》,這本書我買了有好長好長一段時間了,書頁都已經(jīng)泛黃,烙下了時間的痕跡,但我仍然還沒有把這本書讀完。說來慚愧啊。
為什么呢?總感覺這本書的中文翻譯有點拙劣,讀起來煩悶枯燥。明明感覺作者說得非常有道理,但就是提不起半點興致。
(說完這句話,總覺得有點對不住這本書的譯者,畢竟吐槽容易,分享難啊。)
為什么要說這些廢話呢,因為怕大家覺得這是不值一提的細節(jié),但往往細節(jié)決定成敗啊。大家不妨換一種比較輕松的心態(tài)來讀一讀。反正我是不怎么喜歡高談闊論的文章,讀完后往往只能感慨一句:“說得不錯啊”,但也僅此而已。
好了,來步入正題。
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);
}
大家用“肉眼”看完上面這段代碼后,會覺得有問題嗎?
如果不細心的話,好像真的很難發(fā)現(xiàn)“復(fù)制-粘貼”引發(fā)的這個問題:第二個 while 循環(huán)的條件中使用了之前的變量 iterator,而不是它應(yīng)該使用的
iterator1(粘貼后遺漏了變量的修改)。這個問題將會導(dǎo)致代碼在運行的時候拋出java.lang.UnsupportedOperationException
的錯誤。
說句實在話,在敲代碼的這十年來,沒少復(fù)制粘貼,沒少因為粘貼后變量沒有修改徹底,而導(dǎo)致出現(xiàn)了各種意料之外的 bug。
假如把變量的作用域最小化的話,還真的能夠減少這種因為“復(fù)制-粘貼”而導(dǎo)致出現(xiàn)的錯誤。比如說把 while 循環(huán)改造成 for 循環(huán)。
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 循環(huán)使用了和第一個 for 循環(huán)一模一樣的代碼,連 iterator 這個變量也不需要修改了。
從另一方面來看的話,for 循環(huán)比 while 循環(huán)更簡短,可讀性更好。for 循環(huán)還有另外一種最常用的寫法,示例如下。
for?(int?i?=?0;?i?<?list.size();?i++)?{
????System.out.println(list.get(i));
}
但這種寫法仍有改進的地方,因為從字節(jié)碼的角度來看,每次循環(huán)都要調(diào)用一次 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() 方法雖然簡短,但也有消耗啊。都有什么消耗呢?說幾個專業(yè)名詞大家感受一下,比如說:創(chuàng)建棧幀、調(diào)用方法時保護現(xiàn)場、調(diào)用方法完畢后恢復(fù)現(xiàn)場。
(容許我尷尬一下,在寫這篇文章之前,我一直用的上面這種 for 循環(huán)格式??磥韺懳恼逻€是能夠督促自己進步啊。)
怎么改進呢,看下面這種寫法(強烈推薦?。?。
for?(int?i?=?0,?n?=?list.size();?i?<?n;?i++)?{
????System.out.println(list.get(i));
}
在 for 循環(huán)內(nèi)部聲明兩個變量:i 和 n,n 用來保存 i 的極限值,這樣就減少了 size() 方法的調(diào)用次數(shù)(僅有一次了)。
再來看一段代碼。
String?pre_name?=?"沉默";
String?last_name?=?"王二";
System.out.println(pre_name);
System.out.println(last_name);
上面這段代碼看起來挺規(guī)整的,沒什么問題,對吧?它沒有遵守約定——將局部變量的作用域最小化。
pre_name 變量的作用域結(jié)束的有點晚;last_name 變量的作用域開始的有點早。假如第一個 System.out.println()
出錯的話,last_name 的聲明就變得毫無意義了。
(這只是一個例子,變量的處理方法可能比 System.out.println() 復(fù)雜得多。)
好的寫法應(yīng)該是下面這樣子。
String?pre_name?=?"沉默";
System.out.println(pre_name);
String?last_name?=?"王二";
System.out.println(last_name);
有人可能覺得這不是在吹毛求疵嗎?真不是的,變量就應(yīng)該是在第一次使用它的時候聲明。否則的話,變量的作用域要么開始的太早,要么結(jié)束的太晚。
好了,這篇文章到此就結(jié)束了,非常的簡短,但講清楚了“為什么要將局部變量的作用域最小化”。
?
熱門工具 換一換