將局部變量的作用域最小化
優點:增強代碼的可讀性和可維護性,並降低出錯的可能性;
最有力的方法就是在第一次使用它的地方聲明,幾乎每個局部變量的聲明都應該包含一個初始化的表達式, 如果還沒有足夠的信息進行初始化,就應該推遲這個聲明;
for(i,each)循環都支持聲明循環變量,所以當循環終止後不再需要循環變量的內容時,for循環就優先於while循環;
for-each循環優先於for-i循環
使用(集合)迭代器iterator或(數組)for-i的循環變量,由於循環變量會使用多次,且容易和外部的變量搞混,導致出錯;for-each則隱藏了循環變量,避免了混亂和出錯的可能;
性能優勢,對於數組索引的邊界值只計算一次。
對比下面代碼:
for-each不僅可以遍歷集合和數組,還可以遍歷任何實現Iterator接口的對象
簡潔性 ,預防bug,且沒有性能損失;
無法使用for-each情況:
1. 過濾:如果需要遍歷集合,並刪除選定的元素,就要使用顯示的迭代器,以便調用其remove方法 ;
2. 轉換:如果需要修改集合或數組的值,就需要集合的迭代器或數組索引了;
3. 平行迭代:如果需要並行的遍歷多個集合或數組,就需要用到迭代器或數組索引;
了解和使用類庫
看下面求隨機數方法:
這個方法表面看起來是沒有什麼問題的,然而,實際上卻有三個缺點:
1. 如果n是2的乘方,一段周期後,它生成的隨機數將重複;
2. 如果n不是2的乘方,則平均起來,有些數出現的更加頻繁,尤其n比較大時 ;
3. 極少數情況下會返回一個指定範圍外的數,如生成的隨機數為Integer.MIN_VALUE,Math.abs會返回負數MIN_VALUE,如下:
修正方案是使用Random的nextInt(int n)方法;
而如果要自己修正就需要了解偽隨機數生成器,數論和2的求補算法的相關知識;
使用類庫的優點:
1. 充分利用前人的知識和經驗;
2. 不必浪費時間處理與工作不太相關的底層細節;
3. 有專人或組織維護,性能隨著版本的迭代會不斷提高,bug也會被逐漸發現並修復,功能也會不斷豐富;
所以說不管是java還是Android,及時關注官方的api更新說明還是很有必要的,因為如果你不知道這些類庫或者新增加的功能, 可能會花費百倍千倍的時間去寫一些沒有必要,甚至可能有bug的的代碼,總之不要重複造輪子;
如果需要精確的答案,請避免使用float和double
float,double為科學計算和工程計算而設計,執行二進位浮點運算,然而它們並沒有提供精確的結果,尤其不適合用於貨幣計算;例如下面代碼:
修正方案:用BigDecimal代替double:
然而BigDecimal用起來比較麻煩,而且效率和性能要低(要創建BigDecimal對象) 另一種方法是使用int或long代替(根據具體數值大小決定),例如上面例子中的數值都*100;
BigDecimal還有一個優點就是可以完全控制捨入,並提供了8種捨入模式;
還有一點就是9位數以內用int,18位以內用long,超過18位就必須用BigDecimal了;
基本類型優先於裝箱基本類型
基本類型只有值,而裝箱基本類型不同的對象可以有相同值,相同值的可以是不同的對象;
基本類型只有功能值,裝箱基本類型還可以為null;
基本類型更節省時間和空間;
任何情況下,當一項操作中混合使用基本類型和裝箱基本類型時,裝箱基本類型就會自動拆箱 如Integer i;if(i1){...},會NullPointerException,正是由於拆箱時發現i==null;而且反覆的裝箱拆箱是會影響性能的,而且還可能導致一些你想不到的問題;
如果其他類型更合適,則儘量避免使用字符串
字符串不適合代替其他的值類型(基本數據類型 & 對象引用);
字符串不適合代替枚舉類型(如前面的第30條);
字符串不適合代替聚集類型(如果一個實體有多個組件,用一個字符串表示這個實體通常是很不恰當的);
字符串不適合代替能力表;
- 例如想設計一個線程局部變量ThreadLocal:
像上面這樣寫可以,但是又一個前提是key不能重複,如果多個線程(無意或故意的)用了相同的key,就會導致這個變量被多個線程共享,安全性很差;可以想下面這樣,各線程通過getKey獲取key,再以這個key進行變量的存取:
當然如果有興趣也可以看一些java.util.ThreadLocal的實現;
當心字符串連接的性能
我們使用字符串經常這樣寫"String name = "Mr."+"劉";",很方便,如果只是少量的使用確實可以;
但是:為連接n個字符串而重複的使用字符串連接操作符(+),需要n的平方級的時間。因為字符串是不可變的,+操作要創建新的對象,並拷貝要相加的兩個對象;
為了獲得可以接受的性能,請用StringBuilder替代String;相加次數越多,性能的差距是成平方級增長的;若有興趣可以寫個for循環列印時間戳試一下;
這一條其實我們剛入行時就都已經知道了,主要是平時開發過程中要注意應用;
通過接口引用對象
如果有合適的接口類型存在,那麼對於參數,返回值,變量和域來說,就都應該使用接口類型進行聲明;只有當用構造器創建某個對象時,才需要真正的引用這個對象的類;如: List list = new ArrayList();或 public void methodA(List list){...};
養成用接口作為類型的習慣,你的程序將更加靈活;(例如有新的實現可以提供更好的性能,可以方便的進行更換);
如果依賴域現實的任何特殊屬性,就要在聲明變量的地方給這些需求建立相應的文檔說明;
不適合的情況:
- 值類String,BigInteger等,一般是final的;
- 對象屬於給於類的框架,就應該使用基類(一般是抽象類),如java.util.TimerTask抽象類;
- 類提供類接口不存在的額外方法,切程序依賴於這些額外方法,如LinkedHashMap;
接口優先於反射機制
核心反射機制java.lang.reflect,提供了"通過程序來訪問關於已裝載的類的信息"的能力;如獲得Constructor,Method,Field;
然而也要付出相應的代價:
- (將編譯期錯誤推遲到了運行時)喪失了編譯時類型檢查的好處,包括異常檢查,且調用不存在或不可訪問的方法時,將在運行時失敗;
- 執行反射所需要的代碼非常笨拙和冗長,寫著乏味,讀著困難;
- 性能損失,調用反射方法比普通方法慢了許多;
反射功能通常只是在設計時被用到,普通的應用程式在運行時不應該以反射的方式訪問對象;
有些複雜的應用程式需要使用到反射機制,如瀏覽器,對象監視器,代碼分析工具,解釋型的內嵌式系統, 他們必須用到編譯時無法獲取的類,但是存在適當的接口或者超類,這種情況,可以通過反射創建實例, 通過接口或超類以正常的方式訪問這些實例,如果所用構造器不帶參數,可以考試使用Class.newInstance, 而非java.lang.reflect;
謹慎的使用本地方法
JNI(Java Native Interface)允許java程序調用本地方法(native method, 指本地程序語言c/c++編寫的方法);
native方法的三種用途:
- 提供"訪問特定於平臺的機制"的能力,如訪問註冊表,文件鎖 (隨著ava的發展已經在逐漸完善這些功能,如java,util.prefs提供了註冊表的功能,SystemTray提供訪問桌面系統託盤區的能力);
- 提供訪問遺留代碼庫的能力,從而可以訪問遺留數據;
- 通過本地語言編寫程序中注重性能的部分,提高程序性能(不值得提倡,隨著ava的發展性能已經被不斷的優化了);
native方法的缺點:
- 本地語言是不安全的,所以,使用本地方法的應用程式也不再能避免受內存毀壞錯誤的影響;
- 本地語言是平臺相關的,所以,使用本地方法的程序也不再是可自由移植的;
- 比較難調試;
- 進入和退出本地代碼時,需要一定的開銷,所以如果只是使用本地代碼做少量工作,反而可能降低性能;
- 需要"膠合代碼"的本地方法編寫起來單調乏味;
謹慎的進行優化
3條優化相關的格言:
- 很多計算上的過失都被歸咎於效率,而不是任何其他原因,甚至包括盲目的做傻事;
- 不要去計較效率上的一些小小的得失,在97%的情況下,不成熟的優化才是一切問題的根源
- 優化方面應該遵守兩條規則:
1. 不要優化;
2. (僅針對專家)還是不要進行優化;(可能你認為程序慢的地方並沒有問題;可以使用性能剖析工具檢測代碼)
總結:優化弊大於利,特別是不成熟的優化;
要努力編寫好的程序而不是快的程序,不要因為性能而犧牲合理的結構;
(再多底層的優化也無法彌補算法選擇的不當)
但必須在設計過程中考慮到性能問題:
- 避免那些限制性能的設計決策,尤其是API,線路層wire-level協議,永久數據格式)
- api設計對性能的影響:
1. 公有類型可變,會導致大量不必要的保護性拷貝;
2. 適合用複合模式時卻使用繼承,會導致與超類永遠束縛在一起,限制子類性能;
3. 使用實現類型代替接口,會把程序束縛在具體的實現上,即使將來有更優的實現也無法使用;
遵守普遍接受的命名慣例
包名:以組織的Internet域名開頭,且頂級域名放前面,如com.ljy; 且不應以java,javax開頭;
類和接口名:一個或多個單詞組成(通常是名詞或形容詞),每個單詞首字母大寫,儘量避免使用縮寫,尤其是你自己創造的縮寫;
方法和域的名稱:一個或多個單詞(方法通常用動詞,動詞短語或形容詞),除第一個單詞外首字母大寫;
常量域:唯一推薦使用下劃線的情形;
類型參數名稱:通常由單個字母組成,常用的一般由五種:T表示任意的類型,E表示集合的元素類型,K和V表示映射的鍵和值,X表示異常;(多個時可以這樣:T1,T2,T3...)
我是今陽,如果想要進階和了解更多的乾貨,歡迎關注公眾號」今陽說「接收我的最新文章