45、將局部變量的作用域最小化
將局部變量的作用域最小化,可以增強代碼的可讀性和可維護性,并降低出錯的可能性。
Java允許在任何可以出現語句的地方聲明變量(C語言中局部變量要在代碼塊開頭聲明),要使局部變量的作用域最小化,最好的方法是在第一次使用它的地方聲明。局部變量的作用域從它被聲明的點開始擴展,一直到外圍塊的結束處。
如果在循環終止之后不再需要循環變量的內容,for循環就優于while循環。for循環中變量的作用域范圍更小,可以避免一些復制、粘貼錯誤,并且for循環更簡短、可讀性更強。如:
for(Element e : c) {
doSomething(e);
}
Iterator<Element> i = c.iterator();
while(i.hasNext()) {
doSomething(i.next());
}
若循環測試中涉及方法調用,并且每次迭代都返回相同的結果。應使用下面的方法避免每次迭代中執行冗余計算。
for(int i=0, n=upper(); i<n; i++) {
doSomething(i);
}
46、for-each循環優先于傳統的for循環
for-each循環,通過完全隱藏迭代器或索引變量,避免了調用時混亂和出錯的可能。
例如:打印一對骰子的所有可能情況
enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX }
....
Collection<Face> faces = Arrays.asList(Face.values());
for(Iterator<Face> i=faces.iterator(); i.hasNext(); ) {
for(Iterator<Face> j=faces.iterator(); j.hasNext(); ) {
System.out.println(i.next() + " " + j.next());
}
}
這個程序不會拋出異常,而是打印6種組合(ONE ONE 到 SIX SIX),而不是36種組合。要修正這個錯誤,必須在外部循環的作用域中添加一個臨時變量來保存外部元素。如:
for(Iterator<Face> i=faces.iterator(); i.hasNext(); ) {
Face temp = i.next();
for(Iterator<Face> j=faces.iterator(); j.hasNext(); ) {
System.out.println(temp + " " + j.next());
}
}
若使用嵌套的for-each循環,這個錯誤就可以完全避免。如:
for(Face face1 : faces) {
for(Face face2 : faces) {
System.out.println(face1 + " " + face2);
}
}
for-each循環不僅可以遍歷集合和數組,還可以遍歷任何實現了Iterable接口的對象。但有三種常見的情況無法使用for-each循環:
- 過濾,遍歷集合或數組并刪除選定的元素,需要使用顯式的迭代器。
- 轉換,遍歷集合或數組并替換選定的元素。
- 平行迭代,需要平行的遍歷多個集合或數組(骰子打印6種組合的情況)
47、了解和使用類庫
使用標準類庫的好處:
- 可以充分利用他人的使用經驗
- 不必浪費時間在一些與工作不相關的問題上
- 性能會隨著時間的推移而不斷提高
- 可以使自己的代碼融入主流
java程序員應該熟練掌握和使用java.lang,java.util,java.io包中的內容。
一句話,不要重新發明輪子。
48、如果需要精確的答案,請避免使用float和double
float和double類型在執行二進制浮點運算時,不能得到完全精確的結果,它們不應該被用于需要精確結果的場合。例如:0.4 + 0.2
輸出結果為0.6000000000000001
。 原因可以參考這篇博客 代碼之謎(五)- 浮點數(誰偷了你的精度?)
解決這個問題的辦法是使用BigDecimal進行計算,或轉化為int、long類型(自己處理小數點)。如:
BigDecimal result = new BigDecimal("0.2").add(new BigDecimal("0.4"));
對于任何需要精確答案的計算任務,不要使用float或double。
49、基本類型優先于裝箱基本類型
java中每個基本類型(int、double)都有一個對應的引用類型(Integer、Double),稱作裝箱基本類型。
基本類型和裝箱基本類型的區別:
- 基本類型是值,而裝箱基本類型是對象
- 裝箱基本類型有非功能值null
- 基本類型更節省時間和空間
編程時應注意下面幾種常見的錯誤:
//1. ==操作
public int compare(Integer first, Integer second) {
return first < second ? -1 :(first == second ? 0 : 1); //compare(42,42)結果返回1
}
//2. null
Integer i;
if(i == 42) { //報空指針異常,i初始值為null
...
}
//3. 無意識的裝箱
Long sum = 0L; //無意識的裝箱,性能嚴重下降
for(long i=0; i<Integer.MAX_VALUE; i++ ) {
sum += i;
}
必須使用裝箱基本類型的情況:
- 泛型中的參數化類型
- 進行反射的方法調用時
50、如果其他類型更適合,則盡量避免使用字符串
如果可以使用更加合適的數據類型,或者可以編寫更適當的數據類型,就應該避免用字符串來表示對象。若使用不當,字符串會比其他類型更笨拙、速度更慢、也更容易出錯。不要用字符串來代替基本類型、枚舉類型和聚集類型。
51、當心字符串連接的性能
不要使用字符串連接操作符+
來合并多個字符串,應該使用StringBuilder的append方法。第一種方法的時間復雜度為O(n2),第二種方法的時間復雜度為O(n)。
52、通過接口引用對象
若有合適的接口類型存在,那么對于參數、返回值、變量和域,應該使用接口而不是類進行聲明。這將使程序更加靈活。
53、接口優先于反射機制
反射機制提供了訪問編譯時未知的類的能力,對于復雜的系統編程任務,它是必要的。但它也有一些缺點,比如:喪失了編譯時類型的檢查、代碼冗長、性能較低等。若有可能應該僅僅使用反射機制來實例化對象,而訪問對象則使用編譯時已知的接口或超類。
54、謹慎的使用本地方法
Java Native Interface(JNI) 允許java程序可以調用本地方法—native method,本地程序設計語言(如C或C++)編寫的特殊方法。
本地方法的主要用途有:訪問遺留代碼庫、提高系統性能、訪問注冊表和文件鎖等。本地方法是不安全的、難于調試并且不可自由移植。應該盡量少使用或不使用本地方法。
55、謹慎的進行優化
- 不要因為性能而犧牲合理的結構。要努力編寫好的程序而不是快的程序。
- 不要進行優化,特別是不成熟的優化。
不要費力去編寫快速的程序——應該努力編寫好的程序。在設計API、線路層協議和永久數據格式的時候,一定要考慮性能的因素。若系統不夠快,可使用性能剖析工具找到問題的根源,并設法優化相關的部分。再多的低層優化也無法彌補算法的選擇不當,所以選擇一個好的算法是性能優化的根本。
56、遵循普通接受的命名慣例
java的命名慣例包含在《Java編程規范》(the java language specification)中。
泛型參數類型:T表示任意的類型,E表示集合的元素類型,K和V表示映射的鍵和值類型,X表示異常。任何類型的序列使用T1、T2、T3。