1. 程式人生 > >Effective Java讀書筆記——第八章 通用程式設計

Effective Java讀書筆記——第八章 通用程式設計

本章主要討論:

  • 區域性變數的處理

  • 控制結構

  • 類庫的用法

  • 各種資料型別的用法

  • 反射、本地方法

  • 優化、命名慣例

第45條:將區域性變數的作用域最小化

將區域性變數的作用域最小化,可以增強程式碼的可讀性和可維護性,並降低出錯的可能性。

要使區域性變數的作用域最小化,最有力的方法就是在第一次使用它的地方宣告。

過早地宣告區域性變數不僅會使它的作用域過早地擴充套件,而且結束的過於晚了——如果變數是在“使用它的塊”之外被宣告的,當程式退出該塊之後,該變數仍是可見的。

每個區域性變數都應該包含一個初始化表示式。

下面是一種遍歷集合的首選做法:

for(Element e : c) {
    doSth() ...
}

或者:

for (int i = 0, n = expensiveComputation; i < n; ++i) {
    doSth() ...
}

第46條:for-each迴圈優於傳統for迴圈

首先,for-each迴圈不會有效能損失,其次,可以減少出錯的可能。

看下面的程式碼:

//程式碼會在執行第五次迴圈時丟擲NoSuchElementException
enum Suit {CLUB, DIAMOND, HEART, SPADE}
enum Rank {ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING}

...
Collection<Suit> suits = Arrays.asList(Suit.values()); Collection<Rank> ranks = Arrays.asList(Rank.values()); List<Card> deck = new ArrayList<Card>(); for (Iterator<Suit> i = suits.iterator(); i.hasNext()) { for(Iterator<Rank> j = ranks.iterator(); j.hasNext()) { deck.add(new Card(i.next(),j.next())); } }

而下面的程式碼將列印從“ONE ONE”到“SIX SIX”的6個重複的詞,而不是36種組合:

enum Face {ONE, TWO, THREE, FOUR, FIVE, SIX }
...
Collection<Face> faces = Arrays.asList(Face.values());

for(Iteratoer<Face> i = faces.iterator; i.hasNext()) {
    for(Iterator<Face> j = faces.iterator;j.hasNext()) {
        System.out.println(i.next() + " " + j.next());
    }
}

下面的方式可以修復bug,但不美觀:

for(Iterator<Suit> i = suits.iterator();i.hasNext();) {
    Suit suit = i.next();
    for (Iterator<>Rank) j = ranks.iterator();j.hasNext();){
        deck.add(new Card(suit,j.next()));
    }
}
//而如果用for-each迴圈就能很好的解決
for (Suit suit : suits)
    for(Rank rank : ranks)
        deck.add(new Card(suit,rank));

for-each迴圈的另一個好處是,它不僅可以遍歷集合和陣列,還可以遍歷任何實現Iterable介面的物件:

//Iterable介面
public interface Iterable<E> {
    Iterator<E> iterator();
}

總之for-each迴圈的優勢比for迴圈大很多。但是,以下三種情況沒法使用for-each迴圈:

1、過濾——如果要在遍歷過程中刪除某些元素,就應當使用傳統for迴圈,以便呼叫其remove方法。

2、轉換——如果要在遍歷過程中替換某些元素,就只能使用for迴圈。

3、平行迭代——需要並行遍歷多個集合,需使用傳統for迴圈。

第47條:瞭解和使用類庫

考慮下面的方法:

//如希望產生位於0和某個上界之間的隨機整數
static int random(int n) {
    return Math.abs(rnd.nextInt)) % n;
}

該方法存在幾個問題:

1、若n是個較小的2的乘方,那麼它產生的隨機數將會重複。

2、若n不是2的乘方,那麼有些數會比其他數出現的更加頻繁。

3、如果nextInt()返回Integer.MIN_VALUE,那麼Math.abs也返回Integer.MIN_VALUE,假設不是n不是2的乘方,那麼取模操作符將返回一個負數。

產生上述問題的原因是未考慮偽隨機數、數論和2的求補演算法的相關知識

所以,應當使用Java API 1.2引入的方法Random.nextInt(),該方法已經把上述問題考慮進去。這就是使用標準類庫的好處:產生的問題少,且不必把時間花在底層細節上,另外它們的效能會不斷提高。

總結,不要重複發明輪子。

第48條:如果需要精確的答案,請避免使用float 和double

float和double型別主要為了科學計算和工程計算而設計的,它可以提供較為精確的快速而近似的計算。然而它們並沒有提供完全精確的結果。所以在需要精確計算時,不應該使用float和double。而且 ,它們尤其不適用於貨幣計算

使用貨幣等精確計算應使用BigDecimal、int或者long。

public static void main(String[] args) {
    final BigDecimal TEN_CENTS = new BigDecimal(".10");
    int itemsBought = 0;
    BifDecimal funds = new BigDecimal("1.00");
    for(BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price(TEN_CENTS)) {
        itemBought++;
        funds = funds.subtract(price);
    }
    System.out.println(itemBought + " items bought.");
    System.out.println("Money left over: $" + funds);
}

然而BigDecimal有兩個缺點:使用起來不便;效率低。

解決方式是使用int或是long

public static void main(String[] args) {
    int itemBought = 0;
    int funds = 100;
    for( int price = 10;funds >= price; price += 10) {
        itemBought++;
        funds -= price;
    }
    System.out,println(itemBought + " item bought.");
    System.out,println("Money left over: " + funds + " cents");
}

總而言之,對於精確的計算,請不要使用float或double。如果需要計算小數的計算,請使用BigDecimal,如果只是用整數,且數值不太大,就是用int,如果數值範圍超過了9位十進位制數,就是用long,如果超過了十八位,必須使用BigDecimal。

第49條:基本型別優於對應的裝箱型別

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));

時,期望的輸出時0,結果卻是1。

原因是,當比較first < second的時候,編譯器會先將Integer型別拆箱成基本型別,顯然42 < 42為假,所以執行(first == second ? 0 : 1),而此時first和second將是物件的引用,即它們將比較的為 是不是同一個物件(這就是所謂的同一性問題),結果顯然是假,所以返回1。所以,對裝箱型別使用==幾乎總是錯誤的

解決方式是加入兩個區域性變數,所有比較都在這兩個區域性變數上進行,從而避免了同一性的問題。

Comparator<Integer> naturalOrder = new Comparator<Integer>() {
    public int compare(Integer first,Integer second) {
        int f = first;
        int s = second;
        return f < s ? -1 : (f == s ? 0 : 1);
    }
}

考慮下面的程式:

public class Unbelievable {
    static Integer i;
    public static void main(String[] args) {
        if(i == 42){ 
            System.out.println("Unbelievable");

        }
    }
}

上面的程式會直接丟擲NullPointerException異常,原因是i是一個Integer引用型別,而42是基本型別,當編譯器遇到==操作符的兩端是基本型別和裝箱型別時,會先將裝箱型別自動拆箱。但i還沒有初始化,所以引用為null,引用為null的裝箱型別自動拆箱會直接丟擲NullPointerException異常。解決辦法是把i的型別修改為int。

那麼什麼時候適合使用裝箱型別呢?答案是1、當作為集合中的鍵或值的時候。2、當作為泛型引數的時候。ThreadLocal<Integer>2、在進行反射方法呼叫時必須使用裝箱型別。

第50條:如果其他型別更適合,則儘量避免使用字串

  • 字串不適合代替其他的值型別。一些int、float、BigInteger型別不要用String型別表示;

  • 字串不適合代替列舉型別;

  • 字串不適合代替聚集型別:如果一個實體有多個元件,用一個字串來表示這個實體是不恰當的:String compoundKey = className + "#" + i.next();,這會造成混亂。應當簡單地編寫一個類來描述這個資料集,通常是一個私有的靜態成員類(見第22條)。

  • 字串不適合代替能力表:下面以ThreadLocal類來說明。

ThreadLocal是執行緒區域性變數,可以把它理解成一個Map,該物件在每個執行緒中都維護著自己的變數,通過“Key”就可以獲得相應執行緒的值。吐過把Key設計成String型別的,就會造成如果有兩個執行緒給“key”取得名字是一樣的,那麼這個key就變成了共享變數,這兩個執行緒在通過key讀取值的時候,會發生錯誤。

改進:

public final class ThreadLocal<T> {
    public ThreadLocal(){}
    public void set(T value);
    public T get();
}

總之,如果可以使用更加合適的資料型別,或者編寫更加適當的資料型別,就應當避免用字串來表示物件。

第51條:當心字串連結的效能

字串連線符(+)可以把多個字串合併為一個字串,這是個便利的方式。但它不是和連線多個字串,因為String是不可變類,兩個字串連線時,都要被拷貝。

//不好的方式
public String statement() {
    String result = "";
    for (int i = 0;i < numItem(); ++i) {
        result += lineForItem(i); 
    }
    return result;
}

為了改善效能,應使用StringBuilder代替String,前者是非執行緒安全的:
(StringBuffer已經過時,它是執行緒安全的)

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

第52條:通過介面引用物件

如果有合適的型別介面存在,那麼對於引數、返回值、變數和域來說,就都應該使用介面型別進行宣告。

考慮Vector,它是List介面的一個實現:

//應該這樣宣告,使用介面當做型別
List<Subscriber> subscriber = new Vector<Subscriber>();
//不應該這樣宣告
Vector<Subscriber> subscribers = new Vector<Subscriber>(); 

使用介面作為型別的好處是使得程式更加靈活,當決定改變實現時,只需要改變構造器的名稱即可:

//將上面的第一句的Vector時間改為ArrayList
List<Subscriber> subscribers = new ArrayList<Subscriber>();

第53條:介面優先於反射機制

反射機制(java.lang.reflect)提供了“通過程式來訪問關於已裝載的類的資訊”的能力。給定一個Class例項,亦可以獲得Constructor、Method、Field例項,分別表示Class例項對應的類的Constructor、Method、Field。這些物件提供了“通過程式來訪問類的成員名稱、域型別、方法簽名等資訊”的能力。

例如,Method.invoke可以呼叫任何類的任何物件的任何方法。反射機制允許一個類使用另一個類。即使當前者按編譯的時候後者還根本不存在。然而,反射也有缺點:

  • 喪失了編譯時型別檢查的好處:程式企圖用反射呼叫不存在的或者不可訪問的方法,在執行時它將會失敗。

  • 執行反射訪問所需的程式碼非常笨拙;

  • 效能損失:反射方法的呼叫比普通方法慢了許多。

所以反射機制應該只在設計時被用到。通常,普通應用程式在執行時不應該以反射的方式訪問物件。

第54條:謹慎地使用本地方法

JNI(Java Native Interface)允許Java應用程式呼叫本地方法,所謂本地方法,就是本地應用程式需設計語言(如C或C++)來編寫的特殊方法本地方法在本地語言中可以執行任意的計算任務,並返回Java程式設計語言。

本地方法主要有三個用途:

  • 本地方法提供了“訪問特定於平臺的機制”的能力,如訪問登錄檔和檔案鎖。

  • 本地方法提供了訪問遺留程式碼庫的能力,從從而可以訪問遺留資料。

  • 本地方法可以通過本地語言,編寫應用程式中注重效能部分,以提高系統的效能。

然而,使用本地方法來提高效能的做法不值得提倡。因為如今的JVM越來越快了,對於大多數任務,現在即便不適用本地方法也可以獲得相當的效能。

總之,使用本地方法之前務必三思,極少數情況下會需要使用本地方法來提高效能。

第55條:謹慎進行優化

路。。。

第56條:請遵守普遍接受的命名慣例

略。。。

相關推薦

Effective Java讀書筆記—— 通用程式設計

本章主要討論: 區域性變數的處理 控制結構 類庫的用法 各種資料型別的用法 反射、本地方法 優化、命名慣例 第45條:將區域性變數的作用域最小化 將區域性變數的作用域最小化,可以增強程式碼的可讀性和可維護性,並降低出錯的可能性。 要使區域性變

effective java-讀書筆記- 對於所有物件都通用的方法

第三章 對於所有物件都通用的方法 所有非final方法(equals、hashCode、toString、clone、finalize)都有明確的通用約定,因為它們被設計成是要被覆蓋的,如果不遵守,基於雜湊的集合(HashMap、HashSet、HashTable)可

Effective Java 2 讀書筆記 8 通用程式設計

第45條:將區域性變數的作用域最小化 要使區域性變數的作用域最小化,最有力的方法就是在第一次使用它的地方宣告 幾乎每個區域性變數的宣告都應該保護一個初始化表示式例外,try-catch,因為初始化會被拋異常,那麼在try外宣告,在內部初始化 使方法小而集中,使得一個操作相關

Effective Java讀書筆記 -- :對於所有物件都通用的方法

儘管Object是一個具體類,但是設計Object類主要是為了擴充套件。它的所有非final方法(equals、hashCode、toString、clone和finalize)都有明確的通用約定,因為它們就是被設計成要被覆蓋的。第八條:覆蓋equals時請遵守通用約定   

強化學習(RLAI)讀書筆記表格方法的規劃與學習

第八章:Planning and Learning with Tabular Methods 本章為需要環境的model-based強化學習方法(如DP和啟發式搜尋)和不需要環境的model-free方法(比如MC和TD)提供一個統一的論述框架。基於模型的方法依賴規劃(planning)而無模型

effective java讀書筆記之 第一 建立和銷燬物件

第一條: 考慮用靜態工廠方法代替構造器 1.與構造器不同的第一大優勢在於他們有名字,方便使用者呼叫,特別是對於引數各個不同的構造器相比,更為清楚. 2.不必在每次呼叫時都建立一個新的物件,可以為重複呼叫返回相同的物件,同時減少物件的重複建立,節省

Effective Java 2nd》8 通用程序設計

lin 名稱 基類 http 第8章 遍歷集合 except hashmap value 目錄 第45條 將局部變量的作用域最小化 第46條 for-each循環優先於傳統的for循環 第47條 了解和使用類庫 第48條 如果需要精確的答案,避免使用fl

Linux系統程式設計手冊讀書筆記——3 系統程式設計概念

系統呼叫 系統呼叫使處理器從使用者態切換到核心態 每個系統呼叫都有一個唯一的數字來標識 系統呼叫流程: 引數入棧,傳入外殼函式,外殼函式將引數置入特定暫存器(包括系統呼叫編號),執行中斷指定。核心響應中斷指令,呼叫system_call()里程處

Effective Java讀書筆記8-通用程式設計

     第8章  通用程式設計      第45條:講區域性變數的作用域最小化      要使區域性變數的作用域最小化,最有力的方法就是在第一次使用它的地方宣告。      如果在迴圈終止之後不再需要迴圈變數的內容,for迴圈優先於while迴圈。      for迴圈比

Effective Java讀書筆記10-併發)

     第10章  併發      第66條:同步訪問共享的可變資料      第67條:避免過度同步      第68條:executor和task優先於執行緒      第69條:併發工具優先於wait和notify      第70條:執行緒安全性的文件化   

Effective Java讀書筆記9-異常)

     第9章  異常      第57條:只針對異常的情況才使用異常      設計良好的API不應該強迫它的客戶端為了正常的控制流而使用異常。異常是為了在異常情況下使用而設計的,不要將它們用於普通的控制流,也不要編寫迫使它們這麼做的API。 String[] str

java並發編程的藝術,讀書筆記

java並發編程的藝術final域的內存語義寫final域的重排規則:禁止把final域的寫重排序到構造方法之外,主要包括倆個個方面1)JMM禁止編譯器把final域的寫重排序到構造方法之外2)編譯器會在final域寫之後,構造函數return之前插入一個storestore屏障,這個屏障禁止處理器把fina

Core Java Volume I 讀書筆記--4 對象與類

volume nod sina x86 userinfo -- tar .com ndt 20r拿3角51諼3http://weibo.com/p/1005056264972659 SI境嗇7U侍凡17院http://shequ.docin.com/dpyy387 宦3

Java編程思想》筆記 多態

屬於 his 私有方法 對象 5.1 pri 同名 nal pan 1.向上轉型 把子類引用當作父類引用。(子類對象賦值給父類引用) 2.綁定 確定方法屬於哪個類。 3.前期綁定 程序執行前綁定。 4.後期綁定也叫動態綁定 程序運行時綁定。 5.構造器

java程式設計的邏輯讀書筆記——

類的擴充套件 介面和抽象類 interface 宣告介面 implements實現介面 介面約定的是功能,而不是具體實現 介面中方法的預設修飾符為public abstract 介面中變數的預設修飾符為public static final 介面

Java程式設計思想四版讀書筆記—— 初始化與清理

第五章 初始化與清理 1 用構造器確保初始化 使用構造器(condructor),在建立物件時初始化。分為帶引數的初始化和不帶引數的初始化。 2 方法過載 型別提升(向上提升):int — long — float — double    

Java程式設計思想四版讀書筆記—— 介面

這章介紹了介面卡設計模式和策略設計模式。 第九章  介面 介面和內部類為我們提供了一種將介面與實現分離的更加結構化的方法。 1、抽象類和抽象方法 public abstract void f(); 建立抽象類是希望通過這個通用介面操縱一系列類。如果一個類

Effective java 學習】:對於所有物件都通用的方法

第八條:覆蓋equals是請遵守通用約定 滿足下列四個條件之一,就不需要覆蓋equals方法: 類的每個例項本質上都已唯一的。不包括代表值的類,如:Integer,String等,Object提供的equals方法就夠用了 不關心是否提供了“邏輯相等”的測試功能。對

Java程式設計思想讀書筆記——:訪問許可權控制

第六章 訪問許可權控制 初學Java的時候很納悶,為什麼要提供各種訪問修飾,都用public不就行了,程式都能執行,還多省事 我感覺如果這個程式只有你自己去編寫,去維護,那麼其實訪問許可權可以忽略 但是,比如說我寫了一個第三方開源庫,有很多很多的人在使用我的庫,那麼如

Effective Java8 通用程式設計

本章主要涉及Java語言的具體細節,討論了局部變數的處理,控制結構,類庫的用法,各種資料型別的用法,以及兩種不是語言本身提供的機制(reflection和native method,虛擬機器JVM提供支援的)的用法。 1.將區域性變數的作用域最小化 【Item