Effective java 總結

用靜態工廠方法代替構造器的最主要好處

1.沒必要每次都建立新的對象java

Boolean.valueOf編程

Long.valueOfapi

 

2.直接返回接口的子類型,對於外界來講並不須要關心實現細節,主要知道這個接口就行數組

Collections.unmodifiableList數據結構

......併發

 

爲何避免使用終結方法

1.終結方法不會被及時執行app

2.不一樣jvm上實現不一樣eclipse

3.可能根本不會執行jvm

4.在其中拋出的異常會被忽略ide

5.性能差

 

 

 

什麼時候使用:

1.做爲釋放資源的備用方法

FileInputStream:

protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {

            /*
             * Finalizer should not release the FileDescriptor if another
             * stream is still using it. If the user directly invokes
             * close() then the FileDescriptor is also released.
             */
            runningFinalize.set(Boolean.TRUE);
            try {
                close();
            } finally {
                runningFinalize.set(Boolean.FALSE);
            }
        }
    }

2.調用native方法釋放native Object

3.調用父類的終結方法,由於子類覆蓋父類,父類的方法可能不會被調用

 

 

equals方法

1.沒法在擴展可實例化類的同時,既增長新的值組件,同時有保留equals約定.

因此若是子類equals傳入了父類實例,卻不是子類的實例,應該返回false.

 

2.equals不該該依賴於不可靠資源,equals方法應該對駐留在內存中的對象執行肯定性的計算.

 

其餘:

float比較能夠用Float.compare

double同理

 

hashcode

1.覆蓋equals,也應該覆蓋hashcode.

2.相等的對象應該有相等的hashcode,有相等的hashcode的對象不必定是equals的

3.eclipse能夠自動生成hashcode,通常不用本身寫

 

clone

1.最好不要擴展Cloneable接口,而是用其餘方法克隆,好比公司用BeanCopier

其餘:clone雖然返回Object類型對象,可是數組clone的時候可以自動轉型不須要額外操做...下面代碼是沒有編譯錯誤的

 

 String[] s = new String[]{"1"};
 String[] s2 = s.clone();

 

 Comparable接口

1.有序集合contains可能會調用compareTo方法,而非有序集合contains則可能會調用equals來判斷. (例子??)

 

使可變性最小化

不可變類的條件:

1.不一樣修改對象狀態的方法(成員域沒有set方法)

2.類不會被擴展(final修飾)

3.全部域都是final的

4.全部域都是私有的

5.外部不能得到不可變對象內部可變對象的引用,或者沒有指向可變對象的域

可是感受有些條件是多餘的,好比String是不可變類,可是他就有個hash域,不是final的,反正只要這個類的域(狀態)不可能被修改就能夠了.

 

接口優於抽象類

1.實現了接口的類能夠把對於接口方法的調用,轉發到一個內部私有類的實力上,這個私有內部類擴展了骨架實現類.

好比List接口有個ListIterator<E> listIterator(); 方法, ArrayList中內私有內部類private class Itr implements Iterator<E>就實現了這個方法.

同時,這是私有內部類的一種用法,用來實現接口,Itr 依賴於外部的ArrayList,不能獨立存在,可是又能夠有本身額外的一些方法的實現.

 

接口只用於定義類型

1.常量接口模式是對接口的不良使用,實現常量接口會致使把實現細節泄露到該類的導出API中

 

用函數對象表示策略

1.由於策略接口被用做全部具體策略實例的類型,因此咱們並不須要爲了導出具體策略,而把具體策略類作成共有的.相反,宿主類還能夠導出共有的靜態域,其類型爲策略接口,具體的策略類能夠是宿主類的私有嵌套類.

策略模式使用的時候通常是: 策略接口名稱 引用名 = 調用具體策略的得到方法.

因此外界使用的時候是使用接口作爲類型的而不是具體的策略類名.這樣不須要關心策略的具體實現細節,也是多態的表現.

好比 private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable

String類的CaseInsensitiveComparator是私有的靜態嵌套類,外界不可能得到這個類的引用,可是能夠從String的共有的域public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

來得到Comparator接口CaseInsensitive的實現.

同時,這是靜態內部類的一種用法,也是用來實現接口,與Itr依賴於外部的ArrayList不一樣的是這個Comparator不依賴於外部的String,因此是static的.而且不少時候是做爲類的靜態變量導出,而不是實例的成員域

因此說實現接口的時候若是是用內部類來實現的話,若是實現接口須要用到外部類的實例的域的話,那應該使用private class,若是依賴於外部的類的話可使用private static class.另外就是非靜態內部類的是依賴於外部類的實例的,因此若是外部類有static類型的域那是不可能指向非static的內部類的..

 

優先考慮靜態成員類

1.靜態成員類的一種常見用法是做爲共有的輔助類,僅當與它的外部類一塊兒使用時纔有意義.

好比前面String的CaseInsensitiveComparator.

再好比把枚舉類定義在類內部同樣(例子??).

 

優先考慮靜態成員類

1.靜態類和非靜態類的例子:

HashMap中的static class Entry<K,V> implements Map.Entry<K,V>, Entry不須要用到HashMap中的域

HashMap中的private final class KeySet extends AbstractSet<K>, KeySet須要用到HashMap中的域,好比size

 

請不要在新代碼中使用原生態類型

消除非受檢警告

1.數組具備協變的特性,而泛型沒有,泛型是具體的.

String extends Object, 因此String[] 是Object[]的子類型, 而List<String>卻不是List<Object>的子類型

建立泛型,參數化類型,類型參數的數組(List<E>[], List<String>[], E[])是非法的(建立實例new是非法的,引用名字是能夠的), 例外是使用無限制通配符?

 

2. 類型參數T使用通配符?時,只能放null元素

 

3.由於類型擦除,因此instanceof和類文字(XXX.class)中必須使用原生類型

 

優先考慮泛型方法

1.泛型方法被調用的時候大多數時候是不須要制定類型參數的,這是因爲類型推斷的結果,可是構造方法是個例外,必須指明類型參數的具體值.

例如 Map<String, String> map = new HashMap<String, String>();由於是構造方法,全部必須指明類型參數爲String.

 

2.經過包含類型參數的表達式類限制類型參數是容許的,這就是遞歸類型限制

例如:public static <T extends Comparable<T>> T max(List<T> list){....}

 

利用有限制通配符來提高API靈活性(泛型最有用的一節)

1.由於泛型不是協變的,因此若是方法參數是List<T> list,而你傳入的list的類型參數不是T而是T的子類型的話就會報錯.

解決辦法是在方法的參數中的類型參數中使用通配符,好比ArrayList的addAll方法:

 

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

 

這裏使用Collection<? extends E>而不是Collection<E>就是這個緣由,使用了這個通配符之後泛型就彷佛有了像數組同樣協變的特性.

同理? super XXX也是同樣的道理,popAll若是容許傳入一個Collection,並將pop掉的全部元素都放入那個Collection的話那參數就應該寫成

 

public void popAll(Collection<? super E> c){....}

 

書上總結出的規律就是PECS(producer-extends consumer-super),剛纔addAll是生產,popAll是消費

當方法的參數是帶參數類型的時候均可以考慮使用通配符

 

2.有一個小技巧,通配符?的集合好比List<?>是不能插入null之外的元素的,可是能夠將這個集合傳入另一個帶參數List<T>的方法,用那個方法去add元素

書上的例子:

 

public static void swap(List<?> list, int i, int j){
    swapHelper(list, i, j);
}    

public static <E> void swapHelper(List<E> list, int i, int j){
    list.set(i, list.set(j, list.get(i)));      
}

 

3.當有限制的通配符趕上遞歸類型限制的話會比較麻煩,可是PECS仍是有用的

 

package test;

import java.util.Arrays;
import java.util.List;

public class GenericTest2 {
    public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
        return null;//具體的實現省略.....
    }

    public static void main(String[] args) {
        max(Arrays.asList(new MyNumber()));//不使用?extends和super的最通常狀況
        max(Arrays.asList(new MyInteger()));//若是不是Comparable<? super T>而是Comparable<T>的話就會報錯,由於MyInteger extends的是Comparable<MyNumber>而不是Comparable<MyInteger>
        GenericTest2.<MyInteger> max(Arrays.asList(new MyPositiveInteger()));//若是不是List<? extends T>而是List<T>的話就會報錯,由於T在這裏是MyInteger
    }
}

class MyNumber implements Comparable<MyNumber> {
    @Override
    public int compareTo(MyNumber o) {
        return 0;//具體實現省略....
    }

}

class MyInteger extends MyNumber {

}

class MyPositiveInteger extends MyInteger {

}

 

用enum代替int常量

1.枚舉和int或者String常量相比最大的優點就是它是個類,能夠有方法和本身的屬性,能夠提供一些行爲.

 

2.枚舉是不可變的,因此成員域應該爲final

 

3.若是枚舉有通用性,那就該成爲頂層類(public),不然能夠寫在另外1個類中做爲成員類

 

4.若是每一個枚舉對象都有本身獨特的行爲的話,就是說枚舉類中的方法在每一個枚舉對象中都是不一樣實現的話能夠考慮把這個方法寫成abstract的(枚舉類中能夠定義abstract的方法),而後每種枚舉對象都實現這個方法.

好比:

 

enum Operation {
    PLUS {
        @Override
        double apply(double a, double b) {
            return a + b;
        }
    },
    MINUS {
        @Override
        double apply(double a, double b) {
            return a - b;
        }
    },
    TIMES{
        @Override
        double apply(double a, double b) {
            return a * b;
        }
    };

    abstract double apply(double a, double b);
}

 

5.若是多個枚舉對象共享同一種行爲,另外多個枚舉對象共享另一種行爲,能夠考慮使用枚舉策略,在其內部再定義一個枚舉類.意思就是說枚舉類中的abstract至關因而接口方法,枚舉對象覆蓋這個方法至關因而接口的實現.

好比:

 

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 p) {
        payType = p;
    }

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

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

        abstract double overtimePay(double hours, double payRate);

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

 

 

PayType至關於就是一個策略接口,它的2個實現至關因而具體的策略.若是不用PayType這個內部枚舉類的話可能就要寫switch case了....

 

6.枚舉的構造方法在static區塊以前被調用,這個很神奇,可是也能夠理解,由於枚舉類的實例至關因而final且惟一的,會被優先建立....

 

enum A {
    
    A1("a1"), A2("a2");
    static EnumTest2 test2;
    String s;
    public String toString() {
        return s;
    }
    
    private A(String s){
        this.s = s;
        System.out.println(s);//在static方法以前被調用
    }
    
    static{//枚舉建立對象在static以前
        System.out.println("static");
    }
}

 

先輸出a1,a2再輸出static

 

 

用實例域代替序數

1.獲取枚舉實例的定義順序的時候可使用ordinal方法,可是最好仍是構造枚舉對象的時候傳一個數字做爲參數更好,而不是依賴於實例定義的順序.

 

用EnumSet代替位域

1.方法傳遞多個配置項的時候最好不用多個int的|操做,而是使用enumset傳遞多個enum做爲參數.

int bold = 1 << 0;

int italic= 1 << 1;

傳遞 bold | italic 不如傳遞2個枚舉

 

用EnumMap代替序數索引 

1.若是有類型依賴於多個枚舉常量,那它也能夠寫成新的枚舉,實例構造方法傳遞含有以前的枚舉常量做爲成員域,而不是寫個數組簡單的包含多個以前的枚舉(可使用EnumMap,key強制爲枚舉類型).

由於數組的話就依賴於數組的下標,維護起來比較麻煩.

 

註解優於命名模式

1.方法名稱這樣的命名模式好比使用註解來的方便,並且註解能夠添加額外信息.

好比junit testXX方法好比使用@Test註解.不會影響原本的方法命名.

 

 用標記接口定義類型

1.接口在編譯時就能被檢查,而註解能夠在運行時獲取更多的信息.

2.接口定義好之後很難再去修改,而註解能夠不斷增長新的信息

3.接口和類只能被類擴展,而註解能夠用於任何元素

 

檢查參數的有效性

1.public方法可使用@throws說明拋出的異常

2.非public方法使用assert檢查參數,由於這些方法只能是你本身調用,外部使用者不可能會調用,我本身寫的方法我能保證調用參數是正確的,因此可使用assert儘快發現錯誤.

3.錯誤應該儘快發現,儘早檢查.

 

必要時進行保護性拷貝

1.能夠先copy對象再檢查對象的有效性,由於你在檢查對象的有效性時,別的線程可能會改變這個對象.

2.當對象進入你的數據結構之後可能發生變化的話,可能也須要進行拷貝,由於傳給你的對象的調用者在以後可能會修改這個對象

 

慎用重載

1.方法調用的時候選擇哪一個重載方法是在編譯期根據引用的類型來判斷的,而不是引用指向的對象的類型來判斷的.(在編譯期就能肯定調用哪一個重載方法)

2.對於重載方法的選擇是靜態的在編譯期根據引用類型來肯定的,對於覆蓋方法的選擇是動態的在運行期根據對象類型來肯定的.

3.爲了不混亂,儘可能不要處處躲個具備相同參數數目的重載方法

 

慎用可變參數

1.當可變參數列表與基本類型數組結合起來的時候會有一些問題.

 

public class ParamterTest1 {
    public static void main(String[] args) {
        Arrays.asList(new int[] { 1, 2, 3, 4 });
        Arrays.asList(new Integer[] { 1, 2, 3, 4 });
        System.out.println(Arrays.asList(new int[] { 1, 2, 3, 4 }));
        System.out.println(Arrays.asList(new Integer[] { 1, 2, 3, 4 }));
    }
}

輸出
[[I@6dc57a92]
[1, 2, 3, 4]

 

問題在於new Integer[]{....}傳入方法的時候被當作一個Integer的數組,泛型T是Integer

而new int{}{...}傳入方法的時候被看成一個只有 一個元素爲int[] 的數組,泛型T是int[]

緣由是由於泛型T只能是引用類型,不能是基本類型,因此T只能是int[]而不能是int.

 

返回零長度的數組或者集合而不是null

1.可使用Collections下的類來幫助實現.

 

將局部變量的做用域最小化

1.局部變量應該在使用到它的地方聲明並直接初始化,而不是所有在方法第一行直接所有定義好.

2.for比while好的一個地方在於while裏用到的便利有時候須要定義在while外側.致使做用域變大,而for循環裏的變量的做用域只限於for,因此CVfor循環的代碼通常不會出錯,而while代碼CV起來不注意的話可能會有問題.

 

for-each循環優先於傳統的for循環

1.for-each可能效率會比通常的for高一點,主要仍是簡單,不容易出錯,可是要在便利過程當中修改集合的話,仍是須要使用迭代器.

 

第48條:若是須要精確的答案,請避免使用float和double

1.精確計算小數可使用bigdecimal..可是操做比較麻煩速度也比較慢..若是速度要求較高的話能夠把小數擴大必定倍數之後用int和long來計算..可是也很麻煩.

 

第52條:經過接口引用對象

1.若是能夠應該使用接口引用對象而不是類.由於這樣可使程序更加靈活. 若是接口引用周圍的代碼須要依賴具體的實現類的細節,那麼在接口引用的地方用註釋說明.由於這個時候代碼其實與接口的具體實現是相關的.

2.當使用接口引用對象的時候更換具體的實現是很是簡單的..更換具體的實現類的緣由多是由於新的實現能夠提供更好的效率.

 

第57條:只針對異常的狀況才使用異常

1.不該該用異常控制正常程序的流程.

本來的公司就有這樣的問題,經過throw不一樣的異常來返回上層方法.而後後面需求改了,有些時候這些異常即便throw了也要繼續下去.而後代碼就控制不了了,由於發生異常之後就會返回上層方法調用,或者進入catch代碼塊跳過異常後續的代碼.

2.若是要有狀態相關的方法.能夠才用狀態測試方法和可識別的返回值兩種方法代替異常

我通常在方法的參數裏傳入dto而後dto裏有個success標註此次方法執行的狀態.供外層方法檢測.

 

第58條:對可恢復的狀況使用受檢異常,對編程錯誤使用運行時異常

 1.若是異常是能夠恢復的,那就是用受檢異常,若是異常是不能夠恢復的,那就是用runtimeexception和它的子類.

2.寫類繼承throwable是不必的,能夠直接繼承exception

 

第60條:優先使用標準的異常

經常使用異常:

IllegalArgumentsException 非NULL參數值不正確

IllegalStateException 對於方法調用,對象狀態不適合

NullPointerException 在禁止傳入null的狀況下傳入了null值

IndexOutOfBoundsException 下表參數值越界

ConcurrentModificationException 在禁止併發修改的狀況下檢測到對象被修改

UnSupportOperationException 對象不支持用戶請求的方法

 

第61條:拋出與抽象相對應的異常

1.高層的方法在必要的時候能夠try catch底層方法拋出的異常而且從新throw一個高層的異常,由於底層的異常太底層了有時候很難明白究竟是啥錯誤,爲啥會有這個錯誤

2.若是底層的異常是有幫助的,那能夠利用異常鏈,throw一個高層異常,同時cause裏傳入底層的異常.

 

第64條:努力使失敗保持原子性

1.由於拋出異常的時候須要輸出出錯的信息,因此就要儘可能保持對象的狀態是原始的狀態而不是修改後的狀態.

保持對象狀態不變有一些方法:

不可變的對象

在操做以前檢查有效性

調整操做順序讓可能會拋出異常的操做先於改變對象的操做

編寫一段恢復對象的代碼

操做對象以前備份

2.不是全部的時候都必定要這麼作,由於有時候代價會很大,並且有時候對象就是會被修改.

 

第74條:謹慎地實現Serializable接口

1.實驗得出:當子類實現Serializable父類沒實現,而父類有沒有無參構造方法的時候readObject方法就會報錯

2.1的同時若是父類有無參構造方法,那子類能夠反序列化,可是父類的屬性都沒有值,都本身去寫readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out)方法

3.當序列化反序列化出現錯誤的時候(不知道啥錯誤)不能正確初始化成員域的時候會使用readObjectNoData()代替readObject(ObjectInputStream in)方法(雖然我以爲1萬年都用不到)

4.writeReplace方法先於writeObject方法調用,readResolve後於readObject方法調用.因此能夠分別替換寫入和讀取的對象可能會用於單例.

 

第75條:考慮使用自定義的序列化形式

1.不少狀況須要本身寫readObject和writeObject去覆蓋默認的序列化實現,由於對象中還有其餘對象的引用,會遍歷到其餘對象,直到遍歷全部對象爲止.可能會消耗過多資源..若是對象是個值類.那序列化可能沒啥問題,否則可能就須要本身去覆蓋默認的序列化行爲.

2.即便是private的屬性,由於序列化的緣由之後也會像public的api同樣,不能夠隨意修改.

3.實驗得出:反序列化的時候並無調用構造方法.可是會調用父類的構造方法.

 

第76條:保護性地編寫readObject方法

1.若是序列化的類的屬性裏有private的其餘類,那反序列化的時候須要對這個類進行保護性拷貝(代價就是這個屬性類不可能被聲明爲final了,由於要拷貝,二次賦值),以防止被修改(能夠修改字節流,額外增長引用指向以前private的類,而後用這個引用修改private類達到目的).另外在拷貝完成以後進行字段的校驗.

 

第77條:對於實例控制,枚舉類型優先於readResolve

1.若是單例類的屬性是非transient的那麼修改字節流可讓它被反序列化的時候被替換,原理就是編寫一個其餘類,readResolve返回一個新的值,而後修改字節流,屬性引用指向這個新寫的類.當單例類被反序列化的時候會先反序列化它的屬性類,它的屬性類指向新寫的修改類,修改類的readResolve就會被調用.

2.解決這個問題能夠將屬性加上修飾詞transient或者將類設計成枚舉類(可是絕大部分狀況下這個類的成員域可能在編譯的時候是不知道的.這樣就不能設計成枚舉類).

 

第78條:考慮用序列化代理代替序列化實例

1.反序列化是用到了語言以外的特性的.咱們可使用java語言自帶的功能來完成反序列化.原理就是寫一個private的內部類.外部類序列化的時候writeObject實際上是寫入了內部類的對象.而後內部類的readResolve方法能夠返回外部類的實例.這樣至關因而已知一個內部類對象,copy一個外部類對象.

即便僞造字節流也能夠免受影響,由於內部類對象到外部類對象的copy是咱們本身手動用代碼完成的.

相關文章
相關標籤/搜索