7.通用程序設計_EJ
第45條: 將局部變量的作用域最小化
該條目與第13條(使類和成員的可訪問性最小)本質上是類似的。要使局部變量的作用域最小化,最有利的方法就是在第一次使用它的地方聲明。在每個局部變量的聲明處都應該包含一個初始化表達式。還要使方法小而集中。
第46條: for-each循環優於傳統的for循環
傳統的for循環的叠代器和索引變量在每個循環中會出現三次,這很容易出錯。考慮下面的例子:
public class Suits { public static void main(String[] args) { // TODO Auto-generated method stubCollection<Suit> suits = Arrays.asList(Suit.values()); Collection<Rank> ranks = Arrays.asList(Rank.values()); List<Card> deck = new ArrayList<>(); for(Iterator<Suit> i = suits.iterator(); i.hasNext(); ){ Suit suit = i.next();for(Iterator<Rank> j = ranks.iterator(); j.hasNext(); ){ // deck.add(new Card(i.next(), j.next())); System.out.println(suit + " " + j.next()); } } System.out.println("-----------更好的方法---------------"); for(Suit suit : suits){for(Rank rank : ranks){ System.out.println(suit + " " + rank); } } } } enum Suit {CLUB, DIAMOND, HEART, SPADE} enum Rank {ACE, DEUCE, THREE , FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING} class Card { private Suit suit; private Rank rank; Card(Suit suit, Rank rank){ this.suit = suit; this.rank = rank; } }
如果打開代碼中的註釋,我們會發現一個bug,叠代器對外部的集合(suits)調用了太多次的next方法,導致程序結果不是我們想要的。修復這個bug可以在外層添加 Suit suit = i.next();這段代碼。但也有更好的方式,使用for-each循環,如上面代碼中所示。
總之,for-each循環在簡潔性和預防bug方面有著傳統for循環無法比擬的優勢。但下面三種情況無法使用for-each循環:
1.需要遍歷集合,並刪除選定的元素
2.需要遍歷數組或列表,並取代它部分或全部的值
3.需要並行遍歷多個集合
第47條:了解和使用類庫
JDK中內置了大量的工具類庫,但很多“不為人知”。這實際上考研的是編程人員對Java基礎的掌握程度,例如輸出數組的方法:Arrays.toString等等,再比如判斷是否字符串為空是實際上有isEmpty方法的。書中建議每個程序員都應該熟悉java.lang、java.util。
在進行工程項目類的開發時,不應重復造輪子,利用現有的已成熟的技術能避免很多bug和其他問題。除非自己業余愛好研究,重復造輪子我認為就很能提高編程水平了。
第48條:如果需要精確答案,請避免使用float和double
float和double類型不適合用於貨幣計算。因為要讓它們精確地表示0.1(或10的任何其他負數次方值)是不肯能的。
看下面的例子:
public class FloatTest { public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(1.03 - .42); double funds = 1.00; int itemsBought = 0; for(double price = .10; funds >= price; price += .10){ funds -= price; itemsBought++; } System.out.println("itemsBought: " + itemsBought); //3 System.out.println("Change: " + funds); //0.3999999999999 } }
程序的結果不是我們想要的,按理第一條輸出語句應返回4,第二條返回0.可以用BigDecimal類型代替double。
public class BigDecimalTest { public static void main(String[] args) { final BigDecimal TEN_CENTS = new BigDecimal(".10"); int itemsBought = 0; BigDecimal funds = new BigDecimal("1.00"); for(BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)){ itemsBought++; funds = funds.subtract(price); } System.out.println(itemsBought + " items bought"); System.out.println("Money left over: $" + funds); } }
這裏結果就沒問題了。當然如果不介意記錄十進制的小數點,我們可以以分為單位,而不是以元為單位。
總而言之,如果需要精確答案,不要使用float或double。可以使用BigDecimal,如果性能關鍵,可以使用int或long,9位十進制數以內用int,不超過18位可以用long,超過18位就必須使用BigDecimal。
第49條:基本類型優先於裝箱基本類型
基本類型和裝箱基本類型有三個主要區別:
1.基本類型只有值,而裝箱基本類型具有它們值不同的同一性,換句話說,兩個裝箱基本類型可以具有相同的值,但其對象引用不同。
2.基本類型有功能完備的值,而後者有非功能值-null
3.前者比後者更節省空間
來看一個有嚴重缺陷的例子:
public class IntegerTest { public static void main(String[] args) { Comparator<Integer> naturalOrder = new Comparator<Integer>() { @Override public int compare(Integer first, Integer second) { return first < second ? -1 : (first == second ? 0 : 1); // 修正方案 // int f = first; // int s = second; // return f < s ? -1 : (f == s ? 0 : 1); } }; int result = naturalOrder.compare(new Integer(42), new Integer(42)); System.out.println(result); //返回1 對裝箱基本類型做==操作,執行對象同一性比較,導致結果與預期不一致 } }
那什麽時候該用裝箱基本類型呢?第一是作為集合中的元素、鍵和值。第二在參數化類型中用裝箱基本類型作為類型參數。最後,在進行反射的方法調用時,必須使用裝箱基本類型。總之,可以選擇的時候,基本類型要優先於裝箱基本類型。自動裝箱減少了使用裝箱基本類型的繁瑣性,但並沒有減少它的風險。
第50條:如果其他類型更合適,則盡量避免使用字符串
在開發過程中我們不應該為了省事,所有類型都定義為String類型。應該編寫更加適當的數據類型,避免使用字符串來表示對象,若使用不當,字符串類型比其它類型更加笨拙、更加不靈活、速度更慢、也更容易出錯。經常被錯誤地用字符串來代替的類型包括基本類型、枚舉類型和聚集類型。
第51條:當心字符串連接的性能
String字符串是不可變的,每次對一個字符串變量的賦值實際上都在內存中開辟了新的空間。如果要經常對字符串做修改應該使用StringBuilder(線程不安全)或者StringgBuffer(線程安全),其中StringBuilder由於不考慮線程安全,它的速度更快。
第52條:通過接口引用對象
應該優先使用接口而不是類來引用對象,例如:
List<String> list = new ArrayList<String>();
這樣帶來的好處就是可以更換list的具體實現只需一行代碼,之前有談到將接口作為參數的類型,這兩者配合使用就能最大限度實現程序的靈活性。
但如果是類實現了接口,但是它提供了接口中不存在的額外方法,且程序依賴這些額外方法,這個時候用接口來代替類引用對象就不合適了。
第53條:通過接口引用對象
反射機制能在運行時獲取已裝載類的信息,比如Constructor、method、field。但這種方式是有影響的:
喪失了編譯時類型檢查
執行反射訪問所需要的代碼非常笨拙和冗長(這需要一定的編碼能力)
性能損失
所以要慎用反射機制,但如果以非常有限的形式使用反射機制,可以獲得許多好處,還是值得的。比如可以用反射方式創建實例,然後通過它們的接口或超類,以正常的方式返回這些實例。下面程序創建了一個Set<String>實例。
public class Reflective { public static void main(String[] args) { Class<?> c1 = null; String className = args[0]; try { c1 = Class.forName(className); } catch (ClassNotFoundException e) { System.out.println("class not found!"); System.exit(1); } //instantiate the class // @SuppressWarnings("unchecked") Set<String> s = null; try { s = (Set<String>) c1.newInstance(); } catch (InstantiationException e) { System.out.println("class not accessible!"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("class not instantiable!"); System.exit(1); } //exercise the set s.addAll(Arrays.asList(args).subList(1, args.length)); System.out.println(s); try { test(); }catch (Exception e){ System.out.println("catch a exception!"); } } public static void test(){ throw new RuntimeException(); } }
簡而言之,反射機制是一種功能強大的機制,對於特定的復雜的系統編程任務,它是非常必要的。如有可能,應該使用反射機制來實例化類,而訪問對象則用編譯時已知的某個接口或超類。
第54條:謹慎地使用本地方法
所謂的本地方法就是在JDK源碼中你所看到在有的方法中會有“native”關鍵字的方法,這種方法表示用C或者C++等本地程序設計語言編寫的特殊方法。之所以會存在本地方法的原因主要有:訪問特定平臺的接口、提高性能。
實際上估計很少很少在代碼中使用本地方法,就算是在設計比較底層的庫時也不會使用到,除非要訪問很底層的資源。當使用到本地方法時唯一的要求就是全面再全面地測試,以確保萬無一失。
第55條:謹慎地使用本地方法
我在實際編碼過程中,常常聽到別人說,這麽實現性能可能會好一點,少了個什麽什麽性能會好一點,甚至是少了個局部變量也會提到這麽性能要好一點,能提高一點是一點。
但實際上是在編碼中如果你沒有考慮清楚就冒然想當然的去做優化,常常可能是得不償失,就像我開頭提到的那樣,甚至為了優化性能而去減少一個局部變量。正確的做法應該是,寫出結構優美、設計良好的代碼,不是寫出快的程序。性能的問題應該有數據做支撐,也就是有性能測試軟件對程序測試來評判出性能問題出現在哪個地方,從而做針對性的修改。
第56條:遵守普遍接受的命名慣例
我們在編碼時要把標準的命名慣例當做一種內在機制來看待,遵守普遍接受的命名慣例。
7.通用程序設計_EJ