1. 程式人生 > >Java 進階78條學習總結

Java 進階78條學習總結

前言

已經使用Java一年了,使用了主流的Java框架,Spring、SpringMVC、Mybatis等,前端開發也使用了bootstarp、jquery等。除了熟練使用常用的,然而技術並沒什麼長進,工作一年也甚是迷茫,該如何長進呢?
Java的主要技術反而都忘了,只是會百度開發專案,百度總結一些技術點,這怎麼能行?太盲目和不繫統了!遂學習使用主流技術,hadoop、zookeeper、redis、activemq、dubbo等,使用linux系統等。越使用越空虛,越學會使用心裡越沒底。
盲目追求高大上肯定不行!
聽高手意見,看原始碼,遂看Spring原始碼,畢竟經典框架,結合《Spring原始碼深度解析》分析了70%,依然心裡沒底,畢竟跟著別人學啊!自己看Activemq,依然有很多問題,並開始除錯檢視dubbo原始碼,依然有很多問題。
深知基礎太差,就有了詳讀《Effective Java中文版 第2版》的衝動,畢竟是Java經典,而且很多思想和案例基於1.5以後,現在也不落伍。

於是花了一週的時間詳讀,將自己收益的東西記錄下來,不太明白的東西也記錄了一下,希望對喜歡Java的同仁共同學習。
Java之父這樣評價者本書“我很希望10年前就擁有這本書。可能有人認為我不需要任何Java方面的書籍,但我需要這本書!”。

一、建立和銷燬物件

1.考慮用靜態工廠方法代替構造器

靜態的工廠方法,只是一個返回類的例項的靜態方法;
優勢:
- 有名稱更加見名識義;
- 不必每次呼叫時都建立一個新物件;
- 可以返回原返回型別的任何子型別的物件;
- 在建立引數化型別的時候,使程式碼變的更加簡潔;
缺點:
- 類如果不含公有的或者受保護的構造器,就不能被子類化;
- 它與其他靜態方法沒有任何區別;

2.遇到多個構造器引數時考慮用構造器

靜態工廠和構造器有個共同的侷限性,他們都不能很好的擴充套件大量的可選引數。當很多可選引數時,程式設計師習慣使用層疊構造器模式,造成引數很多,還有沒必要的賦值,引數越多程式碼很難編寫,並且可讀性下降。
還有一種方式是新增javaBean方式,使用setter為每個屬性賦值,問題是:構造過程被分到幾個呼叫中,在構造過程中JavaBean可能處於不一致的狀態,使用不一致狀態的物件時會出錯,程式設計師需要保證一致性。把類做成不可變的可能,需要保證其執行緒安全。
推薦使用builder模式:

//程式碼冗長,在引數很多時可用,4個或以上引數更適用
public
class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { // Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int carbohydrate = 0; private int sodium = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } public static void main(String[] args) { NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8) .calories(100).sodium(35).carbohydrate(27).build(); } }
3.用私有構造器或者列舉型別強化Singleton屬性

Singleton指僅僅被例項化一次的類。例如本質上唯一的系統元件,視窗管理器。
之前有兩種方法實現Singleton:
- one:
公有的靜態的最終態的例項物件,構造方法私有化。一旦被例項化,只會有一個Elvis例項,不多也不少。但對於特權使用者的AccessibleObject.setAccessible方法,通過反射機制呼叫私有構造器。如何需要抵禦這種工具可以修改構造器。
- two:
公有成員是個靜態的工廠方法;
但是:如果該類是可序列化的。為了實現Singleton:
- 1)宣告中加上“implements Serializable”;
- 2)宣告所有例項域都是瞬時的,並提供一個readResolve方法。
- 自1.5版本之後的第三種方法:只需要編寫一個包含單個元素的列舉型別;

4.通過私有構造器強化不可例項化的能力

一些工具類不需要被例項化,因為例項化沒有任何意義。

public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
}
5.避免建立不必要的物件

若一個物件具有相同的功能,需要重用性。如果物件始終是不可變的,那它始終可以被重用。
- 1)使用靜態工廠
- 2)靜態的初始化器

6.消除過期的物件引用

如果一個棧先是增長,然後再收縮,那麼,從棧中彈出來的物件將不會被當做垃圾回收,即使使用棧的程式不在引用這些物件,他們也不會被回收。這是因為,棧內部維護著對這些物件的過期引用。
解決方法可以在:
elements[size]=null;

- 一般而言,只要類是自己管理記憶體,程式設計師就應該警惕記憶體洩漏問題。一般元素被釋放掉,則該元素中包含的任何物件引用都應該被清空。

- 記憶體洩漏的另一個常見來源是快取。

  • 記憶體洩漏的第三個常見來源是監聽器和其他回撥。
7.避免適用終結方法
  • 1:在Java中,一般用try-finally塊來完成類似的工作。終結方法的缺點在於不能保證會被及時的執行。java語言規範不僅不能保證終結方法會被及時執行,而且根本就不保證它們會被執行。結論:不應該依賴終結方法來更新重要的持久狀態。如:依賴終結方法來解放共享資源上的永久鎖,很容易讓整個分散式系統垮掉。
  • 2:如果為未被捕獲的異常在終結過程中被丟擲來,那麼這種異常可以被忽略,並且該物件的終結過程被終止。
  • 3:終結方法嚴重的效能損失。

合理使用:

  • 1:顯示的終止方法,與try-finally一起使用,經典例子是Inputstream的close方法。
  • 2:本地對等體,子類終結方法中要顯示的呼叫超類中的終結方法。

二、對於所有物件都通用的方法

講述何時以及如何覆蓋非final的Object的方法;

8.覆蓋equals時請遵循通用約定

不需要使用的情況:
- 1)類的每個例項本質上都是唯一的:如Thread;
- 2)不關心類是否提供了“邏輯相等”的測試功能;
- 3)超類已經覆蓋了equals,從超類繼承過來的行為對於子類也是合適的;
- 4)類是私有的或者包級私有的,可以確定它的equals方法永遠不會被呼叫;
使用的情況:
- 1)值類情形,如Integer、Date、列舉型別。
- 覆蓋原則:自反性、對稱性、傳遞性、一致性;
總結:
- 1)使用==操作符檢查“引數是否為這個物件的引用”。如果是返回true。這只不過是一種效能優化,如果比較操作有可能很昂貴,就值得這麼做。
- 2)使用instanceof操作符檢查引數是否為正確的型別。如果不是返回false。
- 3)把引數轉換成正確的型別。因為轉換之前進行過instanof測試,所以確保會成功。
- 4)對於該類的每個“關鍵”域,檢查引數中的域是否是該物件中對應的域相互匹配。全部測試通過,返回true;
- 5)當你編寫完成equals方法之後,問三個問題:它是對稱的、傳遞的、一致的?
告誡:
- 1)覆蓋equals時總要覆蓋hashCode;
- 2)不要企圖讓equals方法過於智慧;
- 3)不要將equals宣告中的Object物件替換為其他的型別。

9.覆蓋equals時總要覆蓋hashCode

一個常見的錯誤根源是沒有覆蓋該方法,如果不覆蓋的話,導致無法結合所有基於散咧的集合一起正常運作,這樣的集合包括HashMap、HashSet和Hashtable。
原因:如果不覆蓋,導致兩個相等的例項具有不相等的雜湊碼,違反了hashCode的約定。

public int hashCode(){
    return 42;
}

這個問題很大,將所有此類的例項放在同個雜湊桶中,規模很大,不利於效能;

public final class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    private static void rangeCheck(int arg, int max, String name) {
        if (arg < 0 || arg > max)
            throw new IllegalArgumentException(name + ": " + arg);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }

    // Broken - no hashCode method!

    // A decent hashCode method - Page 48
    // @Override public int hashCode() {
    // int result = 17;
    // result = 31 * result + areaCode;
    // result = 31 * result + prefix;
    // result = 31 * result + lineNumber;
    // return result;
    // }

    // Lazily initialized, cached hashCode - Page 49
    // private volatile int hashCode; // (See Item 71)
    //
    // @Override public int hashCode() {
    // int result = hashCode;
    // if (result == 0) {
    // result = 17;
    // result = 31 * result + areaCode;
    // result = 31 * result + prefix;
    // result = 31 * result + lineNumber;
    // hashCode = result;
    // }
    // return result;
    // }

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
    }
}
10.始終覆蓋toString

為了輸出明確,或者解析;

@Override
    public String toString() {
        return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
    }
11.慎重的覆蓋clone

如果是物件允許克隆,首先實現Cloneable,實現clone方法;

12.考慮實現comparable介面

它是Comparable介面的方法comparaTo;
如果實現了就表明它的例項具有內在的排序關係,為的就是實現物件陣列進行排序;

Arrays.sort(a);
public int compareTo(PhoneNumber pn) {
        // Compare area codes
        int areaCodeDiff = areaCode - pn.areaCode;
        if (areaCodeDiff != 0)
            return areaCodeDiff;

        // Area codes are equal, compare prefixes
        int prefixDiff = prefix - pn.prefix;
        if (prefixDiff != 0)
            return prefixDiff;

        // Area codes and prefixes are equal, compare line numbers
        return lineNumber - pn.lineNumber;
    }

四、類和介面

13.使類和成員的可訪問性最小化
  • 設計良好的模組會隱藏所有實現細節,把它的API與它的實現清晰的隔離開來。(資訊隱藏、封裝)
  • 原因:
    1:可以解除組成系統各模組之間的耦合關係,使模組可以獨立的開發、測試、優化、使用、理解、修改。提高了軟體的可重用性。
    實體的可訪問性由它的位置和訪問修飾符共同決定。
    規則:
  • 1:儘可能的使每個類或者成員不被外界訪問。
  • 對於成員(域、方法、巢狀類、巢狀介面)有四種可能的訪問級別,按照可訪問遞增的順序羅列出來:
  • 私有的–>包級私有的–>受保護的–>公有的
14.在公有類中使用訪問方法而非公有域

總結:公有類永遠都不應該暴露可變的域,公有類暴露不可變域危害比這個小,但是有時候會需要用包級私有的或者私有的巢狀類來暴露域,無論這個類是可變或不可變的。

15.使可變性最小化

不可變類只是其例項不能被修改的類。(String/基本型別的包裝類、BigInteger和BigDecimal)
原則:
- 1:不要提供任何會修改物件狀態的方法;
- 2:保證類不會被擴充套件;
- 3:使所有的域都是final的;
- 4:所有的域都是私有的;
- 5:確保對於任何可變元件的互斥訪問;
不可變物件本質上是執行緒安全的,他們不要求同步;

    public static final Complex ZERO = new Complex(0, 0);
    public static final Complex ONE = new Complex(1, 0);
    public static final Complex I = new Complex(0, 1);

總結:不要在構造器或者靜態工廠之外提供公有的初始化方法,除非有很好的理由!保證每個域的可變可以接受!—-此部分需要好好研究!

16.複合優先於繼承

繼承是實現程式碼重用的有力手段。在包的內部使用繼承是非常安全的,在一個程式設計師的控制之下。然而,對普通的具體類進行跨越包邊界的繼承是危險的。
與方法呼叫不同的是,繼承打破了封裝性。(子類依賴於超類,超類的改變影響子類)會帶來一些不可預估的問題,由於版本的區別,子類的使用無法預估了!
解決方法:不用寬展現有的類,而是在新的類中新增一個私有域,它引用現有類的一個例項!

public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;

    public ForwardingSet(Set<E> s) {
        this.s = s;
    }

    public void clear() {
        s.clear();
    }

    public boolean contains(Object o) {
        return s.contains(o);
    }

    public boolean isEmpty() {
        return s.isEmpty();
    }

    public int size() {
        return s.size();
    }

    public Iterator<E> iterator() {
        return s.iterator();
    }

    public boolean add(E e) {
        return s.add(e);
    }

    public boolean remove(Object o) {
        return s.remove(o);
    }

    public boolean containsAll(Collection<?> c) {
        return s.containsAll(c);
    }

    public boolean addAll(Collection<? extends E> c) {
        return s.addAll(c);
    }

    public boolean removeAll(Collection<?> c) {
        return s.removeAll(c);
    }

    public boolean retainAll(Collection<?> c) {
        return s.retainAll(c);
    }

    public Object[] toArray() {
        return s.toArray();
    }

    public <T> T[] toArray(T[] a) {
        return s.toArray(a);
    }

    @Override
    public boolean equals(Object o) {
        return s.equals(o);
    }

    @Override
    public int hashCode() {
        return s.hashCode();
    }

    @Override
    public String toString() {
        return s.toString();
    }
}
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;

    public InstrumentedSet(Set<E> s) {
        super(s);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }

    public static void main(String[] args) {
        InstrumentedSet<String> s = new InstrumentedSet<String>(
                new HashSet<String>());
        s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
        System.out.println(s.getAddCount());
    }
}

InstrumentedSet是一個包裝類,額外增加了一個計數功能!
總結:所以當a確實是b的一個子類時才能繼承,問一句a是b嗎?如果是則繼承。否則複合。
對於不是為了繼承而設計,並且沒有文件說要的外來類進行子類化是很危險的。

17.要麼為繼承而設計,並提供文件說明,要麼就禁止繼承
  • 子類覆蓋父類的每個方法帶來的影響必須精確的描述。說明自用型。
  • 為了繼承而設計類,對於這個類會有一些實質性的限制。
  • 對於一個並非為了繼承而設計的非final具體類在修改了它的內部實現之後,接收到與子類化相關的錯誤報告並不少見。
解決方法:是對於那些並非為了安全地進行子類化而設計和編寫文件的類,要禁止子類化。
  • 1:類宣告為final的。
  • 2:把所有的構造器變成私有的,或者包級私有的,並增加一些公有的靜態工廠來替代構造器。
18.介面優於抽象類

java是單繼承,抽象類作為型別定義受到了極大的限制。
現有的類可以很容易被更新,以實現新的介面;
介面是定義minxin(混合型別)的理想選擇;
介面允許我們構造非層次結構的型別框架;
介面與抽象類結合提供了一個“骨架”:

public class IntArrays {
    static List<Integer> intArrayAsList(final int[] a) {
        if (a == null)
            throw new NullPointerException();

        return new AbstractList<Integer>() {
            public Integer get(int i) {
                return a[i]; // Autoboxing (Item 5)
            }

            @Override
            public Integer set(int i, Integer val) {
                int oldVal = a[i];
                a[i] = val; // Auto-unboxing
                return oldVal; // Autoboxing
            }

            public int size() {
                return a.length;
            }
        };
    }

    public static void main(String[] args) {
        int[] a = new int[10];
        for (int i = 0; i < a.length; i++)
            a[i] = i;
        List<Integer> list = intArrayAsList(a);

        Collections.shuffle(list);
        System.out.println(list);
    }
}

骨架實現的美妙之處在於,它為抽象類提供了實現上的幫助,但又不強加“抽象類被用作型別定義時”所特有的嚴格限制。
- 總結:介面通常是定義允許多個實現的型別的最佳途徑。但當演變的容易性比靈活性和功能更為重要的時候。必須理解和可以接收這些侷限性。公有介面必須經得起全面的測試。

19.介面只用於定義型別

當類實現介面時,介面就充當可以引用這個類的例項型別。類實現了介面,就表明客戶端可以對這個類的例項實施某些動作。為了任何其他目的而定義介面是不恰當的。

例外:常量介面,沒有包含任何方法,只包含靜態的final域,每個域都匯出一個常量。使用這些常量的類實現這個介面,以避免用類名來修飾常量名。常量介面是對介面的不良使用。
- 總結:介面應該只被用來定義型別,它們不應該被用來匯出常量;

20.類層次優於標籤類

標籤類過於冗長、容易出錯,並且效率低下;

class Circle extends Figure {
    final double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    double area() {
        return Math.PI * (radius * radius);
    }
}
class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }
}

標籤類很少使用,要使用層次結構!

21.用函式物件表示策略

總結,函式指標的主要用途就是實現策略模式。在Java中要實現這種模式,要宣告一個介面表示該策略,並未每個具體策略宣告一個實現了該介面的類。當一個具體策略紙杯使用一次時,通常使用匿名類來宣告和例項化這個具體策略類。當一個具體策略是設計用來重複使用的時候,他的類通常就要被實現為私有的靜態成員類,並通過公有的靜態final域匯出,其型別為該策略介面。

class Host{
    private static class StrLenCmp implements Comparator<String>,Serializable{
        public int compare(String s1,String s2){
            return s1.length()-s2.length();
        }
    }
    public static final Comparator<String> STRING_LENGTH_COMPATOR =new StrLenCmp();
    ...
}
22.優先考慮靜態成員類

巢狀類是指被定義在另一個類的內部的類。巢狀類存在的目的應該只是為了外圍類提供服務。
1:靜態成員類、2:非靜態成員類、3:匿名類、4:區域性類;後三種也稱為內部類;
總結:如果一個巢狀類需要在單個方法之外仍然可見,或者它太長了,不適合放在方法內部,就應該使用成員類。如果成員類的每個例項都需要指向其他外圍例項的引用,就把成員類做成非靜態的;否者是靜態的。假設這個巢狀類屬於一個方法的內部,如果你只需要在一個地方建立例項,並且已經有一個預置的型別可以說明這個累的特徵,就要把它做成幾名類;否則,就做成區域性類。

五、泛型

23.請不要在新程式碼中使用原生態型別

如果使用源生態型別,如List而不是List,就失掉了泛型在安全型和表述性方面的所有優勢。
List是源生態型別List的一個子型別,而不是引數化型別List的子型別。使用List失掉了安全性,但如果使用List這樣的引數化型別,則不會。這條規則有兩個小小的例外:
- 源於泛型資訊可以在執行時被擦除;
總結:使用源生態型別會在執行時導致異常,因此不要在新程式碼中使用。原生態型別只是為了與運入泛型之前的遺留程式碼進行相容和互用二提供的。

image

24.消除非受檢警告

最小化警告資訊:
image

25.列表優先於陣列

陣列與泛型相比:
- 1:陣列是協變的。陣列的型別會隨著父子的型別而改變。泛型不會。
合法

Object[] objectArray=new Long[1];
objectArray[0]="i am qiuyadong";

不合法

List<Object> ol=new ArrayList<Long>();
ol.add("i am qiuyadong");

雖然都不會將字串放入Long容器中,但是陣列,會在執行時出錯,列表會在編譯時出錯。
- 2:陣列是具體的;
泛型會通過擦除來實現,只在編譯時強化他們的型別資訊,並在執行時拋棄其型別,為了與之前的混用。
總結:陣列和泛型有著非常不同的型別規則。陣列是協變且可以具體化,泛型是不可變的且可以被擦除的。陣列是執行時的型別安全,但沒有編譯時的型別安全,反之,泛型則也一樣。當它們混合使用時使用列表代替陣列。

26.優先考慮泛型

上述25條並不是一定的,實際上並不是總想在泛型中使用列表。為了提升效能,ArrayList必須在陣列上實現,還有HashMap上實現。
- 總之:使用泛型比使用需要在客戶端程式碼中進行轉換的型別來的更加安全,也更加容易,在設計新型別的時候要確保他們不需要這種轉換就可以使用。這通常意味著要把類做成泛型的。只要時間允許,就把現有的所有的型別都泛型化。

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    // The elements array will contain only E instances from push(E).
    // This is sufficient to ensure type safety, but the runtime
    // type of the array won't be E[]; it will always be Object[]!
    @SuppressWarnings("unchecked")
    public Stack() {
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

    // Little program to exercise our generic Stack
    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        for (String arg : args)
            stack.push(arg);
        while (!stack.isEmpty())
            System.out.println(stack.pop().toUpperCase());
    }
}
27.優先考慮泛型方法

總結:泛型方法就像泛型一樣,使用起來比要求客戶端轉換輸入引數並返回值的方法來的更加安全和容易。

public class GenericSingletonFactory {
    // Generic singleton factory pattern
    private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
        public Object apply(Object arg) {
            return arg;
        }
    };

    // IDENTITY_FUNCTION is stateless and its type parameter is
    // unbounded so it's safe to share one instance across all types.
    @SuppressWarnings("unchecked")
    public static <T> UnaryFunction<T> identityFunction() {
        return (UnaryFunction<T>) IDENTITY_FUNCTION;
    }

    // Sample program to exercise generic singleton
    public static void main(String[] args) {
        String[] strings = { "jute", "hemp", "nylon" };
        UnaryFunction<String> sameString = identityFunction();
        for (String s : strings)
            System.out.println(sameString.apply(s));

        Number[] numbers = { 1, 2.0, 3L };
        UnaryFunction<Number> sameNumber = identityFunction();
        for (Number n : numbers)
            System.out.println(sameNumber.apply(n));
    }
}
28.利用有限制萬用字元來提升API的靈活性

為了獲得最大限度的靈活性,要在表示生產者或者消費者的輸入引數上使用萬用字元型別。如果輸入的引數既是生產者也是消費者,那麼萬用字元型別沒有好處,就需要嚴格的型別匹配。
牢記:PECS表示producer-extends,consumer-super。
此部分確實需要好好看;
- 總結:在API中使用萬用字元型別雖然比較㤇技巧,但是使API靈活的多。如果編寫的是將被廣泛使用的類庫,則一定要適當利用萬用字元型別。
- 所有的comparable和comparator都是消費者。

public class RecursiveTypeBound {
    public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
        Iterator<? extends T> i = list.iterator();
        T result = i.next();
        while (i.hasNext()) {
            T t = i.next();
            if (t.compareTo(result) > 0)
                result = t;
        }
        return result;
    }

    public static void main(String[] args) {
        List<String> argList = Arrays.asList(args);
        System.out.println(max(argList));
    }
}
29.優先考慮型別安全的異構容器

泛型最常用於集合,如set何Map,以及單元素的容器,如ThreadLocal和AtomicReference。它沖淡了被引數化了的容器。但是限制了每個容器智慧有固定數目的型別引數。
總結:集合API說明了泛型的一般用法,限制你每個容器只能有固定數目的型別引數。你可以通過將型別引數放在鍵上而不是容器上來避開這一限制。對於這種型別安全的異構容器,可以用Class物件作為鍵。以這種方式使用的Class物件稱作型別令牌。你亦可以使用定製的鍵型別。例如用DatabaseRow型別表示一個數據庫行,用泛型Column作為它的鍵。

public class Favorites {
    // Typesafe heterogeneous container pattern - implementation
    private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();

    public <T> void putFavorite(Class<T> type, T instance) {
        if (type == null)
            throw new NullPointerException("Type is null");
        favorites.put(type, instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }

    // Typesafe heterogeneous container pattern - client
    public static void main(String[] args) {
        Favorites f = new Favorites();
        f.putFavorite(String.class, "Java");
        f.putFavorite(Integer.class, 0xcafebabe);
        f.putFavorite(Class.class, Favorites.class);

        String favoriteString = f.getFavorite(String.class);
        int favoriteInteger = f.getFavorite(Integer.class);
        Class<?> favoriteClass = f.getFavorite(Class.class);
        System.out.printf("%s %x %s%n", favoriteString, favoriteInteger,
                favoriteClass.getName());
    }
}

六、列舉和註解

30.用enum代替int常量

通過公有的靜態的final域為每個列舉常量匯出例項的類。因為沒有可以訪問的構造器,列舉型別是真正final的。
Java的列舉本質上是int值,它們是例項受控的,是單例的泛型化,實質上是單元素的列舉。
將方法或域新增到列舉型別中,
首先,是想將資料與它的常量關聯起來;

public enum Planet {
    MERCURY(3.302e+23, 2.439e6), VENUS(4.869e+24, 6.052e6), EARTH(5.975e+24,
            6.378e6), MARS(6.419e+23, 3.393e6), JUPITER(1.899e+27, 7.149e7), SATURN(
            5.685e+26, 6.027e7), URANUS(8.683e+25, 2.556e7), NEPTUNE(1.024e+26,
            2.477e7);
    private final double mass; // In kilograms
    private final double radius; // In meters
    private final double surfaceGravity; // In m / s^2

    // Universal gravitational constant in m^3 / kg s^2
    private static final double G = 6.67300E-11;

    // Constructor
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }

    public double mass() {
        return mass;
    }

    public double radius() {
        return radius;
    }

    public double surfaceGravity() {
        return surfaceGravity;
    }

    public double surfaceWeight(double mass) {
        return mass * surfaceGravity; // F = ma
    }
}

將不同的行為與每個列舉常量關聯起來被稱作特定於常量的方法實現;

public enum Operation {
    PLUS("+") {
        double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        double apply(double x, double y) {
            return x / y;
        }
    };
    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }

    abstract double apply(double x, double y);

    // Implementing a fromString method on an enum type - Page 154
    private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>();
    static { // Initialize map from constant name to enum constant
        for (Operation op : values())
            stringToEnum.put(op.toString(), op);
    }

    // Returns Operation for string, or null if string is invalid
    public static Operation fromString(String symbol) {
        return stringToEnum.get(symbol);
    }

    // Test program to perform all operations on given operands
    public static void main(String[] args) {
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
    }
}

策略列舉:

enum PayrollDay {
    MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(
            PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(
            PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

    private final PayType payType;

    PayrollDay(PayType payType) {
        this.payType = payType;
    }

    double pay(double hoursWorked, double payRate) {
        return payType.pay(hoursWorked, payRate);
    }

    // The strategy enum type
    private enum PayType {
        WEEKDAY {
            double overtimePay(double hours, double payRate) {
                return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
                        * payRate / 2;
            }
        },
        WEEKEND {
            double overtimePay(double hours, double payRate) {
                return hours * payRate / 2;
            }
        };
        private static final int HOURS_PER_SHIFT = 8;

        abstract double overtimePay(double hrs, double payRate);

        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            return basePay + overtimePay(hoursWorked, payRate);
        }
    }
}

總結:當需要一組固定常量的時候,與int常量相比,列舉型別的優勢是不可言喻的。列舉要易讀,也更加安全,功能強大。雨多列舉都不需要顯示的構造器或者成員,但許多其他列舉則受益於“每個常量和屬性的關聯”以及“提供行為受這個屬性影響的方法。只有極少數的列舉受益於將多種行為與單個方法關聯。在這種相對極少見的情況下,特定於常量的方法要優於啟動自由值得列舉,如果多個列舉常量同時共享相同的行為,則考慮策略列舉。

31.用例項域代替序數

許多列舉天生就與一個單獨的int值相關聯。所有的列舉都有一個ordinal方法,返回每個列舉常量在型別中的數字位置。這是有問題的,維護和擴充套件很麻煩,若使常量與數字關聯如下:

public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(
            8), DOUBLE_QUARTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);

    private final int numberOfMusicians;

    Ensemble(int size) {
        this.numberOfMusicians = size;
    }

    public int numberOfMusicians() {
        return numberOfMusicians;
    }
}

關於Enum規範中的ordinal:“大多數程式設計師都不需要這個方法。它是設計成用於像EnumSet和EnumMap這種基於列舉的通用資料結構的。”除非你在編寫的是這種資料結構,否則最好完全避免適用ordinal;

32.用EnumSet代替位域
public class Text {
    public enum Style {
        BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
    }

    // Any Set could be passed in, but EnumSet is clearly best
    public void applyStyles(Set<Style&g