Effective Java : 通用程序設計

45.將局部變量的作用域最小化

簡介

13 條,使類和成員的可訪問性最小化,是一個道理,可以採取如下幾種辦法:

  1. 在第一次使用它的地方聲明.
  2. 幾乎每個局部變量的聲明都應該包含一個初始化的表達式,否則(沒有足夠的信息來對一個變量進行有意義的初始化),就應該延遲這個聲明.
  3. for循環優於while循環,而且防止了“剪切-粘貼”錯誤,且更加簡短可讀
  4. 最後一種方法是將局部變量的作用域最小化的方法是使方法小而集中

46.for-each優於for循環

簡介

利用 fro-each循環,對數組的索引邊界值只會計算一次.
並且 看起來很 簡潔

下面是一個示例:

for(Iterator<Suit> i = suits.iterator();i.hasNext()){
    for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
        deck.add(i.next(),j.next());
    }
}

這段代碼,看似沒問題,其實在內層循環中,會多次調用 i.next(),有可能拋出NoSuchElementException,正確的用法應該是;

for(Iterator<Suit> i = suits.iterator();i.hasNext()){
    Suit suit = i.next();
    for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
        deck.add(suit,j.next());
    }
}

而用for-each,就會變成這樣

for(Suit suit:suits){
    for(Rank rank: ranks){
        deck.add(suit,rank);
    }
}

小結

建議如果 編寫的類型表示的是一組元素, 應該讓它實現 Iterable

不能用for-each 的場景

在需要獲取 數組或者迭代器索引的時候.

47.瞭解和使用類庫

不正確實例

產生位於 0某個上界之間的隨機整數.常見代碼,如下:

private static final Random rnd = new Random();
static int random(int n){
    return Math.abs(rnd.nexInt())%n;
}

這個方法有三個缺點:

  1. 如果n 是一個比較小的 2 的盛放,經過一段週期,隨機數序列會重複
  2. 如果n 不是2 的平方,平均起來,有些數比其他數出現的更爲頻繁
  3. 極少數情況下,會失敗

幸運的是,在Java標註庫中已經有了相關實現,nextInt(int),因此可以充分利用這些標準類庫.

小結

總而言之,不要重新發明輪子,如果要做的事情十分常見,有肯能標準類庫中某個類已經完成了這樣的工作.

48.如果需要精確的答案,避免使用float和double

簡介

float 和 double 是爲了提供較爲精確的 快速近似計算而精心設計的.
不適合用於貨幣計算

解決辦法:

  1. 使用BigDecimal,與使用基本運算類型相比,BigDecimal不是很方便,而且很慢.
  2. 除了使用BigDecimal,還用一種辦法是使用 int或者 long,者取決於所涉及的數據的大小

小結

  1. 精確計算避免使用floatdouble,要使用BigDecimal,這樣可以控制舍入,
  2. 如果不介意自己記錄十進制小數點,可以使用 int或者 long
  3. 如果數值超過 18位數字,則必須使用 BigDecimal

49.基本類型優先於裝箱基本類型

簡介

Java 類型系統包含 基本類型引用類型,每一種基本類型都有一個對應的引用類型,稱爲裝箱基本類型,Java1.5 版本提供了自動裝箱自動拆箱

基本類型與裝箱類型的區別

  1. 基本類型只有值,而裝箱基本類型則具有與他們的值不同的同一性
  2. 基本類型只有功能完備的值,而裝箱類型除了功能值之外,還有非功能值null
  3. 基本類型通常比裝箱基本類型更節省時間和空間.

警醒

  • 裝箱基本類型運用== 操作符 幾乎總是錯誤的.
        Comparator<Integer> naturalOrder = new Comparator<Integer>() {
            public int compare(Integer first, Integer second) {
                return first < second ? -1 : (first == second ? 0 : 1);
            }
        };

        naturalOrder.compare(new Integer(42),new Integer(42));//返回1
  • 基本類型和裝箱類型混合使用,會自動拆箱,而null的拆箱操作會拋出NullPointException
public class Unbelievable {
    static Integer i;

    public static void main(String[] args) {
        if (i == 42)//拋出異常
            System.out.println("Unbelievable");
    }
}
  • 包裝類型,會創建過多的對象,造成性能問題
//這是之前第5個item中的代碼
public static void main(String[] args) {
    Long sum = 0L;//這裏每次都會構造一個實例,多創建了2^31個實例. 
    for (long i = 0; i < Integer.MAX_VALUE; i++) { 
        sum += i; 
    } 
System.out.println(sum); }

小結

  1. 基本類型優先於 裝箱類型,基本類型更加簡單快速
  2. 使用裝箱類型,要考慮其自動拆箱的問題
  3. 警惕 ==

50.如果其他類型更適合,則儘量避免使用字符串

簡介

字符串 常被用來表示文本 .因爲其通用性,就出現瞭如下一些不適合的場景

字符串不適合代替其他的值類型.

  1. 當一段數據從網絡,文件或者輸入設備進入到程序之後,就應該轉化爲其本來的類型.而不是繼續使用String
  2. 字符串不適合代替枚舉類型(枚舉類型比字符串更加適合用來表示枚舉常量)
  3. 字符串不適合代替聚集類型(拼接字符串場景)
  4. 字符串不適合代替能力表(ThreadLocal的例子)

小結

  1. 如果可以使用更加合適的數據類型,就 應該避免使用字符串`來表示
  2. 字符串如果使用不當,比其他類型更加笨拙,速度慢.

51.當心字符串連接的性能

簡介

字符串連接符 是將多個字符串合併爲一個字符串的遍歷途徑.
爲連接 n 個字符串而重複地使用連接符,需要n 的平方級的時間(這是由於字符串不可變導致的)
爲了獲得可以接受的性能,請使用StringBuilder

考慮如下兩種情況:

 public String statement() {
    String result = "";
    for (int i = 0; i < numItems(); ++i) {
      result += lineForItem(i);
    }
    return result;
  }
  public String statement() {
    StringBuilder stringBuilder = new StringBuilder(numItems * LINE_WIDTH);
    for (int i = 0; i < numItems(); ++i) {
      stringBuilder.append(lineForItem(i));
    }
    return stringBuilder.toString();
  }

後者前者85 倍之多.

原因:
第一種隨項目數量而呈平方增加,第二種則是線性增加
第二種,默認分配一個足夠容納結果的 StringBuilder,即使使用默認大小,也比第一種快50倍之多.

52.通過接口引用對象

簡介

40 條中,建議使用 接口 作爲 參數類型
其實,應該是 優先使用接口, 而不是類引用對象.
如果有合適的接口類型存在,對於 參數,返回值,變量,都應該使用接口類型來聲明.

示例

// 使用接口引用指向子類對象,good
 List<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
// 子類引用指向子類對象,bad
 Vector<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
//使用接口的好處之一,就是可以方便的更換實現類
 List<SubscriptSpan> mSubscriptSpen = new ArrayList<SubscriptSpan>();
  • 如果沒有合適的接口存在,完全可以用類而不是接口來引用對象.

不存在相應接口的情況:

  1. 值類(很少會有多個實現),final
  2. 對象屬於一個框架,而框架的基本類型是類.
  3. 類實現了接口,但是提供了接口中不存在的額外方法.

53.接口優先於反射機制

簡介

反射提供了一種能力: 通過程序來訪問已裝載的類的信息的能力,即使被編譯的時候後者還不存在,然而,

反射的代價:

  • 喪失了編譯時類型檢查的好處(包括異常檢查). 如果程序企圖 用反射調用不存在或者不可訪問的方法,將會運行失敗.
  • 執行反射訪問所需要的代碼非常笨拙和冗長: 編寫乏味,閱讀也困難
  • 性能損失: 反射的 執行比普通方法調用要 慢一些.

反射的場景

  • 反射通常只在設計時被用到,普通應用在運行時不應該以反射方式訪問對象.
  • 以反射的形式創建實例,然後通過它們的接口或者超類,已正常方式訪問這些實例.

小結

反射機制是一種功能強大的機制,但也有一些缺點.
如果編寫的程序必須要與編譯時 未知的類 一起工作,如 有可能,應該僅僅使用反射機制實例化對象,訪問對象時則使用 編譯時 已知的接口或者超類

54.謹慎的使用本地方法

簡介

Java Native Interface(JNI) 允許 java程序調用 本地方法.
本地方法主要有 三種用途:

  1. 提供了訪問特定於平臺的機制
  2. 訪問老的C/C++版本的庫,訪問遺留代碼的能力
  3. 通過本地方法,編寫應用中注重性能的部分

JNI的劣勢

  1. 不安全,內存管理不受JVM控制了
  2. 與平臺相關,自由移植變得困難
  3. 難以調試
  4. Java和native層的交互是有開銷的,如果本地代碼只做少量工作,則會降低性能

小結

總而言之,使用本地代碼要三思,

  1. 極少數情況下需要使用本地方法來提高性能.
  2. 如果必須要使用,也要儘可能全面的測試.

55.謹慎的進行優化

簡介

很多計算上的過失都被歸咎於效率(沒有必要達到的效率),而不是任何其他的原因-- 甚至包括盲目的做啥事                                       
                                --- 

不要去計較效率上的一些小小得失,在 `97%`的情況下,不成熟的優化纔是一切問題的根源
        ---

在優化方面,應該遵循兩條規則

        規則1 : 不要進行 優化.
        規則2 (僅針對專家) : 還是不要進行優化 -- 也就是說,在你還沒有絕對清晰的未優化方案之前,請不要進行優化. 

這些格言,比Java 出現早了 20年,他們講述了優化的深刻真理:

1. 優化的弊大於利,特別是不成熟的優化
2. 要努力表寫好的程序,而不是快的程序
3. 努力避免那些限制性能的設計決策
4. 要考慮`API`設計決策的性能後果
5. 爲了獲得好的性能對`API`進行包裝是一個不好的想法
6. 在每次試圖做優化之前和之後,豆芽對性能進行測量.

小結

總而言之.,不要費力去編寫快速的程序 – 應該努力編寫好的程序,速度自然會隨之而來. 當 構建完系統後,要測量它的性能.

56.遵守普遍的命名規則

簡介

Java 平臺建立了一 整套很好的命名慣例.分爲兩大類: 字面的語法的.

  • 包名要體現出組件的層次結構,全小寫
  • 公佈到外部的,包名以公司/組織的域名開頭,如 com.xxx.xxx
  • 執行某個動作的方法長用動詞或者動詞短語來命名,如 append
  • 轉換對象類型的方法,

小結

把命名規則當做一種內在的機制來看待,並且學者用他們作爲第二特徵.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章