Effective Java中文版

Know Why Never do.html

先知道基本編程基本規則,才知道何時能夠打破java

[TOC]程序員

建立和銷燬對象

靜態工廠方法代替構造器

靜態工廠提交代碼的可讀性,而且讓調用者沒必要爲選擇什麼參數構造器煩惱編程

靜態工廠可以避免建立多的重複對象設計模式

靜態工廠可以返回那些你自定義該對象的子對象(好比private的,更靈活)數組

public class Service{
    private Service(){};//Prevents instantiation
    private static final Map<String,Provider> providers =
        new ConcurrentHashMap<String,Provider>();
    //Provider registraion API
    public static void registerDefaultProvider(Provider p){
        registerProvider(name,p);
    }
    public static void registerProvider(String name,Provider p){
        providers.put(name,p);
    }
    //Provider unregistration API
    public static void unreginsterProviders(){
        providers.remove();//不穿參數所有清空
    }
    public static void unreginsterSelectProvider(String name){
        providers.remove(name);//移除某個
    }
    //Service asscess API
    public static Service newInstance(){
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service new Instance(String name){
        Provider p = providers.get(name);
        if(p==null) throw Exception;
        return p.newService();
    }
}
複製代碼

靜態工廠在參數化類型實例時更加簡潔瀏覽器

Map<String,List<String>> m = new HashMap<String,List<String>();
==>結合泛型
Map<String,List<String>> m = HashMap.newInstance();
public static <K,V> HashMap<K,V> newInstance(){
    return new HashMap<K,V>;
}
複製代碼

常見靜態工廠方法舉例緩存

ValueOf ---返回實例和其參數具備相同值,類型轉換方法
of --- valueOf 簡寫
getInstance --- 惟一的實例
newInstance --- 返回不一樣的實例
getType --- 返回對象類型
newType --- 返回不一樣的對象類型
複製代碼

構造器參數時考慮構造器

建立帶參數實例選擇 優勢 缺點
重疊構造器 適合<=3參數肯定,如自定義view,開銷低 參數過多時,可讀性差,混亂和出錯
JavaBean模式 可讀性好,多參不容易出錯 出現不一致狀態,違法類的不可變原則,線程不安全
Builder模式 適合>3,可讀性強,靈活,參數約束,可擴展 額外開銷,重複代碼

//使用重疊構造器
mNativeAdLoader = new NativeAdLoader(mContext,unitId,"ab:123",超時時間);

//定義重疊構造器
public class NativeAdLoader{
    public NativeAdLoader(Context context,String unitId){
		this(context,unitId,"");
    }
    public NativeAdLoader(Context context,String UnitId,String stragegy){
    	this(context,unitId,stragegy,0)
    }
    public NativeAdLoader(Context context,String unitId,String stragegy,long timeout){
    	this.context = context;
    	this.unitId = unitId;
    	this.stragegy = stragegy;
    	this.timeout = timeout;
    }
}
複製代碼
//使用JavaBean
mNativeAdLoader = new NativeAdLoader();
mNativeAdLoader.setContext(mContext);
mNativeAdLoader.setUnitId(unitId);
mNativeAdLoader.setStrategy("ab123");
mNatvieAdLoader.setTimeout(超時時間);

//定義JavaBean
public class NativeAdLoader{
	public NativeAdLoader(){}
    public void setContext(Context context){
    	this.mContext = context;
    }
    public void setUnitId(String unitId){
    	this.unitId = unitId;
    }
    public void setStrategy(String strategy){
    	this.strategy = strategy;
    }
    public void setTimeout(long timeout){
    	this.timeout = timeout;
    }
}
複製代碼
//使用構鍵器
mNativeAdLoader = new NativeAdLoader.Builder(mContext,"unitId")
    .forNativeAdSourcesByStrategy("ab:123",超時時間)
    .build();

//定義構建器
public class NativeAdLoader{
    
    public static class Builder{
        //必要參數
        protected Context mContext;
        private String mUnitId;
        //可選參數
        private String mStrategy;
        private long mTimeout public Builder(Context context,String unitId){
            this.mContext = context;
            this.mUnitId = unitId;
        }
        
        public Builder forNativeAdSourcesByStrategy(String strategy,long timeout){
            this.mStrategy = strategy;
            this.mTimeout = timeout;
            return this;
        }
        
        public NativeAdLoader build(){
            return new NativeAdLoader(this)
        }
    }
    
    public NativeAdLoader(Builder builder){
        this.context = builder.context;
        this.unitID = builder.unitID;
        this.strategy = builder.strategy;
        this.timout = builder.timeout;
    }
}
複製代碼

強化Singleton屬性

常規化標準單例寫法安全

//Singleton with static factory
public class AdsManager{
    //private(私有不可直接引用) + static(方便靜態方法) + final(最終對象)+ 類加載時實例化 
    private static final AdsManager INSTANCE = new AdsManager();
    private AdsManager(){};//私有化
    public static AdsManager getInstance(){//getInstance 代表該對象的單例性
       	return INSTANCE;
    }
    //Remainder ommitted
}
複製代碼

解決多線程不安全問題(懶加載)性能優化

public class AdsManager{
    private static transient AdsManager mInstance;//瞬時
    private AdsManager(){};
    public static AdsManager getInstance(){
        if(mInstance == null){
            synchronize(AdsManager.class){
                if(mInstance ==null){
                    mInstance = new AdsManager();
                }
            }
        }
        return mInstance;
    }
}
複製代碼

完美枚舉寫法(java 1.5上)

public enum AdsManager{
	INSTANCE;
}
//簡潔可讀,防止屢次實例化,提供序列化機制,沒法反射攻擊
複製代碼

私有構造器強化不可實例化能力

//工具類或者不想要被實例化的類
public class UtilsClass{
    //將默認的構造器私有化並直接拋異常
    private void UtilsClass(){
        throw new AssertionError();
    }
    //Remainder omitted
}
複製代碼

避免建立沒必要要對象

避免屢次建立同一個對象

String s = new String("123"); ==> String s = "123";
//Date,Calendar,TimeZone 只建立一次
Class Person{
    private final Date birthDate;
    
    private static final Date BOOM_START;
    private static final Date BOOM_END;
    //靜態代碼塊寫法,類加載僅執行一次
    static {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(...);
        BOOM_START = gmtCal.getTime();
        getCal.set(...);
        BOOM_END = gmtCal.geTime();
    }
    //壞處若是該方法不用,就會依然建立以上對象
    public boolean isBabyBoomer(){
        return birthDate.compareTo(BOOM_START)>=0&&
               birthDate.compareTo(BOOM_END)<0;
    }
}
複製代碼

避免無心識的自動裝箱,優先使用基本數據類型

public static void main(String[] args){
    Long sum = 0L;//申明時特別注意,避免使用裝箱
    for(long i=0;i<Integer.MAX_VALUE;i++){
        sum += i;
    }
    System.out.println(sum);
}
複製代碼

到底重用仍是建立附加對象考慮安全,風格,性能,清晰,簡潔,功能,不要一律而論

消除過時對象的引用

內存泄漏的常見來源-過時對象

//棧實現類
public class Stack {
    private Object[] elements;
    private int size=0;
    private static final int DEFAULT_INITIAL_CAPACITY=16;
    
    public Stack(){
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    public void push(Object e){
        ensureCapacity();
        elements[size++] = e;
    }
    public Object pop(){
        if(size==0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null;//eliminate obsolete reference
        return result;
    }
    private void ensureCapacity(){
        if(elements.length == size){
            elements = Arrays.copyOf(elements,2*size+1);
        }
    }
}
//什麼是過時引用,活動部分,非活動部分?
//垃圾回收機制會自動回收非活動部份內存,而程序員要作的就是將該過時引用對象告訴垃圾,這是非活動部分。
複製代碼

內存泄漏的常見來源-緩存

1.WeakHashMap 表明弱引用緩存(緩存項過時自動刪除,緩存項生命週期由該鍵的外部對象決定時)

2.不容易肯定緩存的生命週期,緩存隨着時間推移變得愈來愈沒價值,定時清除TimerScheduledThreadPoolExecutor

3.緩存添加新條目時清理,LinkedHashMap.removeEldestEntry

4.更復雜直接使用java.lang.ref???

內存泄漏的常見來源-監聽器或其餘回調

1.沒有顯式的取消註冊

2.不肯定何時取消,只保存回調的弱引用,常見保持成WeakHashMap中的鍵

保證執行終止方法

//io流,connection,DB等
try{
    //do something
}catch(Exception){
    
}finally{//must do
    .close
        .cancel
        	.terminate();
}
複製代碼

全部對象都通用的方法

覆蓋equals時請遵照通用約定

等價關係

自反性(reflexive) x!=null&&x.equals(x) ==>true
對稱性(symmetric) x!=null&&y!=null && x.equals(y) ==>y.equals(x)
傳遞性(transitive)x,y,z!=null && x.equals(y)&&y.equals(z) ==>x.equals(z)
一致性(consistent)x,y!=null &&x,y not change ==>x.equals(y) always true/false
非null性 (nevernull)x!=null && x.equals(null) ==>false
複製代碼

原理篇(略)

高質量equals訣竅

  1. == 代替 equals ,是時候下降成本了

  2. 使用instanceof操做檢查類型

  3. 參數轉換類型(默認進行了instanceof測試)

  4. 這樣寫(先比較最有可能不同的)

    Double.compare Arrays.equals
    field == null? o.field == null : field.equals(o.field);
    若是field 和 o.field 是相同對象的引用,更快
    field==o.field || (field!=null&&field.equals(o.field))
    複製代碼
  5. 寫完equals方法自問本身,是否對稱,傳遞,一致

  6. 覆蓋equals總要覆蓋hashCode

  7. 不要將equals申明的Object替換成其餘類型

    //沒有覆蓋Object.equals
    public boolean equals(MyCleass o){
        ...
    }
    Override 註解好處就會告訴你,編你不過
    複製代碼

覆蓋equals時總要覆蓋hashCode

爲何非要這樣?

1.不這樣違反Object.hashCode的通用約定(若是兩個對象的equals方法是比較相等的,那麼調用這兩個對象任意一個hashCode方法必須產生相同的整數結果)

2.不這樣致使該類沒法結合基於散列的集合正常工做,好比HashMap、HashSet、HashTable

class PhoneNumber{
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;
    public PhoneNumber(...){
        //igore construct
    }
    
    @Override public boolean equals(Object O){
        //igore equals
    }
    
    //Broken - NO HashCode Method
}

Map<PhoneNumber,String> m = new HashMap<PhoneNumber,String>();
m.put(new PhoneNumber(123,456,789),"lizhaoxiong");
==>m.get(new PhoneNumber(110,456,789))  會返回null ***
==>m.get(new PhoneNumber(123,456,789))  會返回"lizhaoixong"嗎?待test

若是一個對象的屬性值發生變化,那麼這個對象的hashCode值還相同嗎?test

複製代碼

解決方案

@Override
public int hashCode(){
    int result = 17;
    result = 31*result + areaCode;
    result = 31*result + prefix;
    result = 31*result + lineNumber;
    resutl result;
}
思路:
隨便找個int 數;
不一樣域轉換int:
boolean(f?1:0)| byte,short,int (int)f |(int)(f^(f>>>32))
    |Float.floatToIntBits(f)|Double.doubleToLongBits(f)+Float.floatToIntBits(f)|
|對象,遞歸調用equals和hashCode。|數組,Arrays.hashCode,遞歸每一個元素hashCode再組合排列
公式:result = 31*result+c;
    

1.爲何是31?
移位和減法代替乘法,JVM優化性能(31*i = (i<<5)-i)
2.直接result int的常亮值的壞處?
線性時間、平方級時間?降級了,散列表 ==> 鏈表
3.怎麼使用延遲初始化? 
if(result=0)才進行計算,上面代碼
4.什麼是散列函數?
5.String、Integer、Date的hashCode方法返回的是什麼確切值?這樣的壞處?
複製代碼

始終要覆蓋toString

爲何非要這麼作?

1.雖然這不是強制的,但這是規範。

2.若是不覆蓋試着比較下 類名@16進制散列 VS 自定義(簡潔、信息豐富、易於閱讀)

3.toString應該返回值得關注的信息診斷信息

4.toString 要麼返回統一規範化格式信息 要麼你註釋說明好

謹慎地覆蓋clone

提供一種無需調用構造器就能夠建立對象思路

拷貝的精確含義取決於該對象的類

要把這個搞懂,必須造成一個專題,就目前而言沒有多大意義

考慮實現Comparable接口

compareTo 和 equals 區別?

1.compareTo沒有在Object聲明

2.compareTo 是Comparable接口惟一方法,實現該接口比較對象數組Array.sort(a)

比較返回的是int類型值

3.當比較對象類型不一樣時直接拋出ClassCastException不進行比較,equals返回false

4.Comparable接口是參數化的,comparable方法是靜態類型,沒必要進行類型轉化和類型檢查,有問題直接沒法編譯,若是參數null,直接NullPointException

5.CompareTo方法中域的比較是順序比較而不是等同性比較。

啥時候用該接口?(collection imp)

因爲Java平臺的全部值類都實現該接口,當你在編寫一個值類須要明顯的內在排序關係,好比字母、數值、時間等,堅定考慮實現該接口

public interface Comparable<T>{
	int compareTo(T t);
}
複製代碼

自定義Comparator專門用於你自定的排序

int 比較><= ,float和double用Double.compare/Float.compare

多個關鍵域比較,從最關鍵的開始,逐步進行到全部重要域,出現非0結果結束返回該結果

public int compareTo(People p){
    if(inner < p.inner) return -1;
    if(inner > p.inner) return 1;
    if(out < p.out) return -1;
    if(out > p.out) return 1;
    ...
        return 0;
}

//肯定差值不大於INTERGER.MAX_VALUE 2^31 -1
public int compareTo(People p){
    int diffInner = inner -p.inner; 
    if(diffInner!=0) return diffInner;
    
    int diffOut = out - p.out;
    if(diffOut!=0) return diffOut;
    return 0;
}
複製代碼

類和接口

類和成員的可訪問性最小化

儘量地下降可訪問性

在公有類中使用訪問方法非公有城

Android 和 java 的公有類的成員變量編寫究竟是追求簡潔仍是安全

class Point {
    public int x;
    public int y;
}

VS

class Point {
	private int x;
	private int y;
	
    public Point (int x,int y){
    	this.x = x;
    	this.y = y;
    }
	
    public void setX(int x){
    	this.x = x;
    }
    public void setY(int y){
    	this.y = y;
    }
    
    public int getX(){return this.x}
    public int getY(){return this.y}
}


//構建時約束條件
public Point (int x, int y){
    if(x<0&&y<0){
		throw new IllegalArgumentException("x,y" + x + y)
    }
    this.x = x;
    this.y = y;
}


複製代碼

可變性最小化

如何把類變成不可變類?(參照String、BigInteger源碼)

  • 不對外提供修改對象狀態的任何方法

  • 保證類不會被擴展,好比聲明final類和private構造器(valueof)

  • 聲明域都是final

  • 聲明域都是private

  • 使用函數對操做數運算並不修改它

    public Complex add(Complex c){
        return new Complex(x+c*x,y+c*y)
    }
    複製代碼

爲何要使用不可變類?

  • 相比可變類的複雜空間和不肯定性,不可變穩定可靠

  • 不可變對象本質是線程安全的,不要求同步

    //爲了鼓勵多使用,應該這樣寫
    public static final Complex ZERO = new Complex(0,0);
    public static final Complex ONE = new Complex(1,0);
    
    ==>近一步單例化,提升內存佔用
    複製代碼
    複製代碼
  • 對象能夠被自由共享(剔除拷貝構造器)

  • 內部信息共享

  • Building blocks

  • 就算是可變類,也要讓其可變性最小(好比TimerTask狀態只有執行和取消)

  • 惟一缺點:建立這種對象代價可能很高,爲提升性能使用可變配套類

    可變配套類
    String 的可變配套類是StringBuilder和淘汰的StringBuffer
    BigInteger 是BitSet
    複製代碼

複合優於繼承

Why?

  • 跨越包邊界的繼承是很是危險的,脆弱
  • 集成打破了封裝性,子類以來超類的實現細節,超類一旦變化,子類遭到破壞

複合/轉發?

想要給現有類增長一個功能/特色,在新類中增長一個私有域,它引用現有類的一個實例,使現有類成爲新類的一個組件,這樣的設計叫作複合(composition)

新類的每一個方法均可以調用被包含的現有類的實例對應的方法,並返回它的結果,稱爲轉發(forwarding)		

在轉發的基礎上,返回結果時作定製化處理,這就是裝飾/包裝(wrapper)
複製代碼

​```java Class A { public void methodA(){}; public void methodB(){}; }

Class BWrapper { private A a; public BWrapper(A a){this.a=a};

public void wrapA(){
    a.methodA();
}
public void wrapB(){
    a.methodB();
}
public void wrapC(){
    a.methodA();
    a.methodB();
}
複製代碼

}

​	包裝類不適合回調框架(SELF問題)

慎用繼承!

用繼承前問問本身,二者 is-a的關係嗎?每一個B確實也是A嗎?不能肯定就不應擴展A。那麼就讓B應該包含A的一個私有實例,而且暴露較小、簡單的API,A本質不是B的一部分,只是它的實現細節。

(Java 違反這一原則的,棧Stack不是向量vector,屬性列表Properties不是散列表Hashtable)	



#### 要麼爲繼承而設計提供文檔說明,要麼禁止繼承

爲何寫基類的要比寫實現的值錢?



好的API文檔應該描述給定方法<u>作了什麼工做</u>,而不是描述<u>如何作到的</u>

使用此類的方法要注意什麼,應該幹什麼,不能幹什麼



類必須經過某種形式提供適當的鉤子(hook)



編寫基於繼承的基類後必定要編寫子類進行測試



構造器決不能調用可被覆蓋的方法 



對於並不是爲了安全子類化而設計的類要禁止子類化,有幾種方法

1. 聲明類爲final
2. 構造器私有化或者包級私有並增長公有靜態工廠代替構造器(單例)
3. 利用包裝類模式代替繼承實現更多功能



#### 接口優於抽象類

**區別**

- 抽象類可包含實現,接口不容許

- 抽象類依賴繼承進行子類實現,因爲Java單繼承原則喪失靈活性

- 接口靈活,擴展性強,而且非層次。高手用繼承,多用接口好

​

**包裝類設計模式體現着點**



**多接口模擬多重繼承**



**骨架實現**(先設計公有接口,可能抽象的基類簡單的公有實現,具體子類實現)

= 簡單實現+抽象類



**公有接口設計切記**:多多測試接口,否則一旦上線發行,就很差修改了



#### 接口只用於定義類型

**常量接口模式**

實現細節泄漏API、可能後期無價值、污染代碼(反面教程ObjectStreamConstants)

**有固定常量的需求** 

1. 枚舉類型

2. 不可實例化的工具類(大量引用類名)

3. 靜態導入

 ​

#### 類層次優於標籤類

#### 函數對象表示策略

函數指針

策略模式

元素排序策略:經過傳遞不一樣比較器函數,就能夠得到各類不一樣的排列順序

函數對象:Java沒有提供函數指針,使用對象引用實現一樣功能,這種對象的方法是對其餘對象的操做,

且一個類導出一個方法,它的實例實際等同於指向該方法的指針,這樣的實例叫作函數對象。 

#### 優先考慮靜態成員類

**嵌套類有四種:**

**靜態成員類(非內部類)**:

特色— 理解爲剛好聲明到類裏面的普通類,可訪問外圍類的成員,聲明私有也可供外圍類內部使用

場景— 公有輔助類,像枚舉,像Calculator.Operation.MINUS

**非靜態成員類(內部類)**:

特色— 每一個實例都包含一個額外指向外圍對象的引用,非靜態成員類強依賴外圍類,並在發行版本中不可由非靜轉靜

場景— Iterator、HashMap的Entry

**匿名類(內部類)**:

特色— 沒有名字,不是外圍類成員,聲明時實例化,不可Instanceof,不可擴展,必須簡短

場景— 動態建立函數對象,匿名的Sort方法的Comparator、建立過程對象Runnable、Thread、TimeTask

、靜態工程內部

**局部類(內部類)**:

特色— 使用少見

場景— 任何能夠聲明局部變量的地方

經典問題:靜態內部類和非靜態內部類的區別?



## 泛型

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

泛型的做用 — 在編譯時期告知是否插入類型錯誤的對象,而非再運行時期在報錯,泛型檢查。

泛型定義 — 聲明一個多個類型參數的類或者接口,java1.5版本開始支撐,例如List<String>。

原生態類型 — 不帶任何類型參數的泛型類型,List<E> 原生態時List,只是爲了和引入泛型以前的遺留代碼兼容

```java
//未使用泛型
private final Collection students = ...;
students.add(new Teather());
for(Iterator i = students.iterator;i.hasNext();){
  Student s = (Student) i.next();//Throws ClassCastException
}
//使用泛型
private	final Collection<Student> students = ...;
students.add(new Teather()); // 提早Error

//for-each 和for
for(Student s: students){}
for(Iterator<Student> i = students.iterator();i.hasNext();){
  Student s = i.next();//No cast necessary        
}
複製代碼

泛型和原生區別 — 泛型檢查

泛型子類化 — List 是原生態類型List的子類型,而不是參數化類型List 的子類型

List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(44));
String s = strings.get(0);
//編譯經過,可是收到警告
private static void unsafeAdd(List list, Object o){
    list.add(o);
}
//沒法編譯經過,List<String> 不是List<Object>的子類
private static void unsafeAdd(List<Object> list,Object o){
    list.add(o);
}
複製代碼

無限制通配符類型 — Set<?> 不肯定和不關心實際參數類型

無限制通配符類型Set<?>和原生態類型Set之間有什麼區別?

安全的!不是什麼都能插入Set<?>,須要聲明 Extends ?

何時使用原生態(泛型信息能夠在運行時被擦除) — 1.類文字 2.和instanceof有關

static void newElements(Set s1,Set s2){
    for(Object o1 : s1){
        if(s2.contains(o1)){...}
    }
} 
//會報錯
static void newElements(Set<?> s1,Set<?> s2){
    for(Object o1 : s1){
        if(s2.contains(o1)){...}
    }
}
//利用泛型來使用instanceof
if(0 instanceof Set){
    Set<?> m = (Set<?>)o;
}
複製代碼

消除非受檢警告

非受檢警告 — 強制轉化、方法調用、普通數組、轉化警告

簡單 ==>多用泛型,便可消除

難的 ==>@SuppressWarnings("unchecked") //必定要使用確認註釋緣由

列表優先於數組

數組和泛型的區別?

  1. 數組是協變且能夠具體化的;泛型是不可變且能夠被擦除的。
  2. 數組和泛型不能同時(混合)使用(泛型數組建立錯誤)

爲何List不是List的超類也不是子類?

由於類型信息已經被擦除了。

爲何第一是列表第二是數組?

錯誤發現原則— 能在編譯時發現就不要放到運行時發現,泛型列表(擦除—編譯時檢查類型信息,運行時擦除類型信息)優先於數組(運行時檢查元素類型約束),這個就像地鐵安檢同樣,你進入地鐵前對你進行檢查(泛型列表),而當你已經進入地鐵後,再來人對你進行檢查(數組),危險極大。

類泛型化

如何學習編寫泛型呢?

  1. 把類型參數Object名稱變成E
  2. 將相應的類型參數替換全部Object類型
  3. 這時會有錯誤或警告,兩種錯誤和解決方案見以下代碼
《具體查看Stack源碼》
elements = new E[DEFAULT_INITIAL_CAPACITY];
==>
@suppressWarnings("unChecked")
elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];

E result = elements[--size];
==>
E result = (E)elements[--size]
==>
@SuppressWarnings("unCkecked") 
    E result = (E)elements[--size];

總之須要你確認沒有安全問題

class DelayQueue<E extends Delayed> implements BlockingQueue<E>;
E被稱之爲有限制的類型參數
每一個類型都是它的子類型
複製代碼

方法泛型化

public static <E> Set<E> union(Set<E> s1,Set<E> s2){
    Set<E> result = new HashSet<E>(s1);
    result.addAll(s2);
    return result;
}

//消除代碼chenyu
Map<String,List<String>> ana = new HashMap<String,List<String>>();
==>
Map<String,List<String>> ana = newHashMap(); 
public static <K,V> HashMap<K,V> newHashMap(){
    return new HashMap<K,V>();
}

//遞歸類型限制Comparable接口
public interface Comarable<T>{
    int compareTo(T o);
}

//根據元素的天然順序計算列表的最大值
public static <T extends Comparable<T>> T max(List<T> list){
    Iterator<T> i = list.iterator();
    T result = i.next();
    while(i.hasNext()){
        T t = i.next();
        if(t.compareTo(result)>0){
            result = t;
        }
    }
    return result;
}
複製代碼

利用限制通配符提高API的靈活性

泛型參數類型特色?

  1. 不可變
  2. List 不是List的子類,但本身是本身的子類

    提升靈活性的兩種方式?

    PECS(producer-extends,consummer-super)

    合理的選擇嚴格的參數類型、生產者參數、消費者參數,包裝類型安全和靈活性

    public class Stack<E> {
        public void push(E e);
        public E pop();
    }
    
    + pushAll
    
    + popAll
    
    //子類沒法push進去,因爲Integer是Numble的子類,可是Integer插入不了聲明爲Numble的
    public void pushAll(Iterable<E> src){
    	for(E e: src){
    		push(e);
    	}
    }
    有限制的通配符類型==>
    public void pushAll(Iterable<? extends E> src){
    	for(E e: src){
    	push(e);
    	}
    }
    //若是聲明E是Object,因爲Object是Numble的父類,可是Numble卻沒法調用Object的
    public void popAll(Collection<E> dst){
    	while(!isEmpty()){
    		dst.add(pop());
    	}
    }
    有限制的通配符類型==>
    public void popAll(Connection<? super E> dst){
    while(!isEmpty()){
    	dst.add(pop())
    	}
    }
    
    
    //升級eg1:
    static <E> E reduce(List<? extends E> list ,Function<E> f);
    //升級eg2:返回類型仍然爲Set<E> ,不要用通配符類型做爲返回類型,除非很是強調靈活性
    public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2) //升級eg3: public static <T extends Comparable<? super T>> T max(List<? extends T> list) //注意類型匹配: max(List<? extends T> list){
    Iterator<? extends T> i = list.iterator();
    }
    
    //無限制的類型參數,不能將元素放回剛剛從中取出的列表
    public static void swap(List<?> list,int i,int j){
        list.set(i,list.set(j,list.get(i)));
    }
    ==>
    調用swapHelper(List<E> list,int i,int j)                                                                                                  
    複製代碼

    優先考慮類型安全的異構容器

    泛型使用場景

    集合(Set、Map、List)、單元素容器(ThreadLocal、AtomicReference)

    1. 特色是每一個容器只能有固定樹木的類型參數
    2. 每一個鍵均可以有一個不一樣的參數化類型
    3. ​ 異構體泛型化Class String.class 屬於Class類型

    如何定製鍵類型呢?

    Map<Class,Object> favorites = new HashMap,Object>()

    枚舉和註解

    用emum代替int/String常量

    爲何?

    1. 硬編碼的書寫錯誤,編譯時不報錯,運行時報錯
    2. int 打印效果不佳
    3. 枚舉 = int + 打印 + 任意添加方法和域
    4. 枚舉的本質是單例泛型化,即單元素的枚舉
    5. 枚舉中覆蓋toString容易打印
    6. 將不一樣的行爲和枚舉常量關聯起來
    7. 沒必要在乎枚舉的性能成本,有機會用就果斷用

    用EnumMap代替索引數組

    1.多數組最好轉化爲Map枚舉集合+泛型設計!

    2.EnumMap內部使用了經過序數索引的數組,並隱藏了實現細節

    3.數組最大的問題是沒法保證序數和數組索引之間的關係

    4.嵌套的EnumMap關係(142頁)EnumMap<..., EnumMap<...>>。

    5.使用Enum.ordinal是傻叉

    //將全部花園的花根據種類打印出來 
    //1. 打印的map、key是type、vlaue是花對象集合(定義)
    //2. 遍歷原有集合(type),重寫put
    //3. == 遍歷花園獲得花對象
    Map<Flower.Type, Set<Flower>> flowersByType = 
        new EnumMap<Flower.Type, Set<Flower>>(Flower.Type.class); 
    for(Flower.Type t: Flower.Type.values()) { --values方法能夠這樣嗎?返回的是key對應的對象嗎?
    	flowersByType.put(t,new HashSet<Flower>());
    }
    for(Flower f:garden){
    	flowersByType.get(f.type).add(f);
    }
    System.out.println(flowerByType);
    複製代碼

    用實例域代替序數引用(ordinal)

    ordinal方法返回每一個常量類型的數字位置(序數)

    該方法被設計給EnumSet和EnumMap使用,請徹底避免自行使用ordinal方法

    // 錯誤事例
    public enum Ensemble{
        ONE,TWE,THTREE...;
        public int numberOfDay(){
        return ordinal()+1;
    	}
    }
    
    // 正確事例
    public enum Ensemble{
        ONE(1),TWE(2),THREE(3)...;
        private int num;
        Ensemble(int day){num = day}
        public int numOfDay(){return num;}
    }
    複製代碼

    用EnumSet代替位域

    爲何?

    1. 避免手工操做錯誤和不雅觀代碼!
    2. EnumSet就是用單個long表示,性能不比位域差
    // bad code
    public class Text{
        public static final int STYLE_BOLD = 1 << 0; //1
        public static final int STYLE_ITALIC = 1 << 1; //2
        public void applyStyles(int styles){...}
        ==> text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
    }
    
    //nice code
    public class Text{
        public enum Style{BOLD,ITALIC... }
        public void applyStyles(Set<Style> styles){...}
        ==> text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC));
    }
    複製代碼

    用接口模擬可伸縮的枚舉

    1.基本數據類型的枚舉如何經過擴展枚舉實現呢

    2.經過Collection<? Extends Operation> 這個有限制通配符類型

    3.雖然沒法編寫可擴展枚舉類型,但能夠經過編寫接口即實現該接口實現進行模擬

    註解優先於命名模式

    命名模式的3個缺陷:

    1. 第三方庫和框架要求指定名稱,結果寫錯了也不報錯,即沒執行卻給人測試正確假象
    2. 沒法確保他們只用於相應的程序元素上
    3. 沒法和將參數值和程序元素關聯

    註解類型:

    @Retention(RetentionPolicy.RUNTIME)  -- 運行時保留
    @Target(ElementType.METHOD) -- 做用域,在方法中才合理
    public @interface Test {} 
    
    //Retention(生命範圍,源代碼,class,runtime)
    //Documented,Inherited,Target(做用範圍,方法,屬性,構造方法等)
    複製代碼
    1. 好程序員多用註解,通常程序員沒機會定義註解類型的
    2. 可是最後有機會使用註解考慮,不少IDE和靜態分析工具或第三方支持庫提供的註解庫
    3. 註解實現的原理是什麼以及如何自定義註解和你常使用的註解

    堅持使用Override註解

    防止因爲重載或者或者無心識地覆蓋產生的覆蓋(有覆蓋需求就加上吧)

    用標記接口定義類型

    究竟是使用標記接口仍是標記註解?

    1. 是不是僅僅供類和接口使用仍是供任何程序元素使用
    2. 是不是永遠限制標記只用於特殊接口元素

    參考:Serializable標記接口(被序列化接口)

    方法

    如何處理參數和返回值?

    如何設計方法簽名?

    如何爲方法編寫文檔?

    --專一可用性、健壯性、靈活性

    檢查參數的有效性

    1.錯誤發生前檢查參數有效性,下降調試工做難度

    2.使用@throws標籤(tag)標明違反參數值限制會拋出的異常,如ArithmeticException

    IllegalArgumentException、IndexOutOfBoundsException、NullPointerException

    3.使用斷言作有效性檢查

    4.只要有效性檢查有一次失敗,那麼你爲此作的努力即可以連本帶利的償還

    /** * @throws NullPointerException if object is null */
    public static <T> T requireNonNull(final T object, final String message) {
        if (object == null) {
            throw new NullPointerException(message);
        }
        return object;
    }
    複製代碼

    必要時進行保護性拷貝

    不要使用Clone進行保護性拷貝

    保護性拷貝是在檢查參數的有效性前進行的

    Date類事可變的,把它交給如Date.getTime()返回long進行時間表示

    謹慎設計方法簽名

    1. 養成良好的命名習慣,方法名稱體現品味
    2. 不要追求便利提供過多方法,簡潔,less is more
    3. 避免過長的參數列表,順序錯誤和難以記憶
      1. 把一個方法拆分紅多個方法,好比List的sublist與indexOf和lastIndexOf方法結合
      2. 建立輔助類(helper class)
      3. 採用Builder模式
    4. 參數類型,優先使用接口而非類,好比咱們參數申明Map而不是HashMap或TreeMap,List非ArrayList
    5. 對於boolean參數或者肯定固定值的參數,使用枚舉類型顯得更加優雅

    慎用重載

    重載是在編譯時期決定的 -- 重載方法當方法名稱相同,在調用時容易形成調此非此。

    如何避免胡亂使用重載 -- 永遠不要使用兩個具備相同參數數目的重載方法。

    在建立多個功能類似的,僅僅傳入參數類型不一樣的方法時,儘量經過方法命名區分而非參數類型區分。

    編寫API時,請不要讓調用程序員感到混淆的,調用不明確,避免重載的致使難以發現的錯誤。

    覆蓋是運行時期的,覆蓋更加安全。-- so 避免編譯時的類型影響

    public static String classify(Collection<?> c){
        return c instanceof Set ? "Set" :
        	   c instanceof List ? "List" : "Unknow Collection"
    }
    複製代碼

    慎用可變參數

    當咱們編寫方法,明確了參數的類型可是不肯定參數的數量的時候,就用可變參數

    可變參數的實現本質sum(int... args) 就是sum(int [] args)

    ==>先建立一個數組,數組的大小爲在調用方法傳遞的參數數量

    //可變參數解決無參數問題
    public static int min(int firstArg,int... args){
            int min = firstArg;
            for(int arg: args){
                min = arg < min?arg:min;
            }
            return min;
    }
    複製代碼

    可變參數是爲printf而設計,最主要的做用體如今printf和反射機制

    Arrays.asList ==> Arrays.toString() 
    複製代碼

    從性能角度,可變參數每次的調用都會致使一次數組的分配和初始化,so 靈活性 VS 性能

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

    每次都得爲空指針考慮,因此請儘可能不要返回null,除非你是有意識的

    永遠不會分配零長度數組的代碼寫法

    //返回零長度的數組
    private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
    public Cheese[] getCheeses[]{
        return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
    }
    //返回空集合
    return Collections.emptyList();
    //具體參考Collections的EmptyList系列的幾個內部類
    複製代碼

    爲全部導出的API元素編寫文檔注射

    How to Write Doc Comments

    爲每一個被導出的類、接口、構造器、方法、域聲明增長文檔註釋

    方法的文檔註釋:

    1. 應當簡潔的說明該方法作了什麼而非它是如何完成的
    2. 該方法的能被調用全部前提條件@throws標籤或者@param和後置條件
    3. 可能產生的負做用,好比啓動了一個後臺線程,那麼該方法的線程安全性須要描述
    4. 標籤齊全和說明規範@param @return(名稱短語,參數或返回值) @throws(if(若是),名稱短語(異常條件))

    ,而且由標籤開頭的文字不用句點結束

    1. @code 標籤{@code index<0} 多行代碼
      {@code index>=0}
    2. @literal(字符)標籤 {@literal |x+y|<|x|+|y|}
    3. 文檔可讀性應當優於源代碼的可讀性

    概要描述(Summary Description)-- 文檔註釋的第一句話,對你寫的API一句話描述體現你的總結能力了

    更多說明:

    1. 當註解、接口、枚舉這種代碼量精簡到爆的時候,文檔註釋要多到爆。
    2. 類的線程安全性和可序列化性是須要被描述的
    3. 接口或者基類的文檔註釋優先級高於超類和實現類
    4. HTML有效性檢查器運行由Javadoc產生的HTML文件
    5. 使用文檔註釋應當強制性、一致風格、統一標準

    通用程序設計

    討論局部變量的處理、控制結構、類庫的用法、各類數據類型的用法以及reflection 和 native method的用法

    及優化和命名慣例

    局部變量的做用域最小化

    1. 在第一次使用它的地方聲明,而不是等到用到該變量的時候,若是變量是在它目標使用區域以前或以後使用的話,後果可能很嚴重哦
    2. 每一個變量的聲明都應該包含一個初始化表達式 (try catch除外)
    3. 若是循環終止再也不須要循環變量內容,for循環優於while(重用元素錯誤和加強可讀性及更簡短)
    4. 把局部變量放到方法裏面或者代碼塊裏面是得做用域最小化,有效避免被另外一個操做

    for-each循環優於for循環

    Why?

    遍歷集合和遍歷數組for循環是比while循環更好,可是因爲迭代器和索引變量的存在很容易出錯

    使用for-each循環徹底隱藏迭代器和索引變量,適合集合和數組

    性能上稍有優點,它對數組的索引邊界值只是計算一次

    嵌套遍歷時,內外數量不一致會拋出NoSuchElementException,嵌套循環可能須要在第一層循環內把遍歷的值記錄下來,而for-each完美的處理了嵌套的問題

    advise!

    若是要編寫類型表示一組元素,即便不讓其實現Collection,也要讓其實現Iterable,這樣就可使用for-each

    沒法使用for-each的三種狀況

    過濾 -- 要刪除指定元素調用remove方法(修改)

    替換 — 遍歷列表或數組時,要取代替換元素時 (修改)

    平行迭代

    瞭解使用類庫

    Random三個缺點 ==> 僞隨機數生成器 Random.nextInt(int) ,So

    1. 相似的標準類庫通過專家認證和無數次的驗證和使用
    2. 不要浪費時間爲那些和工做不相關的問題提供特別解決方案,focus your app,not it
    3. 讓本身的代碼融入主流,更易讀,更易維護,被大多數開發人員重用

    advise!

    1. 不少程序員不使用類庫緣由是不知道,固然Java類庫很龐大,但也要作到熟悉java.lang java.util java.io及每次重要發行版本的新特性,而後在須要的時候進行查閱
    2. 重點關注下Collections Framework(集合框架)
    3. 重點關注下1.5版本的java.util.concurrent包的併發使用工具,簡化多線程的編程任務
    4. 若是你作的事十分常見,請不要重複造輪子

    若是須要精確的答案避免使用float和double

    一種辦法是複雜的使用BigDecimal,不方便較慢,要本身使用十進制小數點(貨幣)

    還有一種辦法,是使用int(簡單)或者long(複雜)

    基本類型優於引用類型

    Java分爲基本數據類型和引用類型,每一個基本數據類型都有一個引用類型

    區別?

    1. 基本數據類型只有值
    2. 引用類型多個null
    3. 基本類型更加節省時間和空間

    對於引用數據類型使用==操做符老是錯誤的

    請把能聲明成基本數據類型變量,避免反覆的裝箱和拆箱,致使性能降低

    何時使用引用類型呢?

    1. 集合中的元素、鍵和值,Java不容許ThreadLocal類型
    2. 反射的方法調用是必須使用裝箱基本類型

    若是其餘類型更合適,儘可能避免使用字符串

    不適合使用字符串的場景:

    1. 它本來是int、float、BigInteger類型、是或否的boolean類型,是什麼就是什麼!
    2. 不適合代替枚舉類型
    3. 不適合代替彙集類型(String compoundKey = className + "#" +i.next()),簡單編寫一個私有靜態成員類吧
    4. 不適合設計成不可僞造惟一的鍵 -- capability,參見ThreadLocal的get()方法,發現key已經隱藏

    小心字符串鏈接的性能

    當是簡單輸出或者顯示使用字符串鏈接 +

    但當拼接的量級別變大,或者使用for循環進行拼接請使用StringBuilder代替String

    多使用StringBuilder的append方法

    經過接口引用對象

    更通常的講,若是有合適的接口類型存在,那麼參數、返回值、變量和域都應該使用接口類型聲明

    養成接口做爲類型的好習慣吧,使你的程序更靈活吧

    Vector<Subscriber> subscribers = new Vector<Subscriber>();
    ==> good code :
    List<Subscriber> subscribers = new Vector<Subscriber>();
    
    好比ThreadLocal類的以前的HashMap 要換成 IdentityHashMap那不是一句話的事,並且不用擔憂影響
    複製代碼

    若是對象的類是基於類的框架(abstract class),就應當用相關的基類來引用這個對象,好比TimerTask

    接口優先於反射機制

    反射提供了經過程序訪問類的成員名稱、域類型、方法等信息的能力

    反射的設計初衷:

    ​ 爲了基於組件的應用建立工具設計,這類工具須要裝載類。普通應用程序在運行時不該該以反射方式訪問對象。目前反射機制使用場景:瀏覽器、對象監視器、代碼分析工具、解釋型的內嵌式系統、RPC系統。現實app中當編譯時沒法獲取到的類或者過期方法等

    反射的缺點:

    1. 喪失編譯時的類型檢查的好處
    2. 反射訪問的代碼很是難以閱讀和笨拙和冗長
    3. 性能損失(受到機器影響,2到50倍)

    使用注意:當你編寫程序與編譯時的未知類一塊兒工做時

    謹慎使用本地方法

    本地方法的三種用途:訪問特定平臺的機制,註冊表和文件鎖,訪問遺留代碼和數據能力,提供系統性能

    使用本地方法提升性能時不值得提倡的,原來是可行的如今隨着JVM的塊和Java平臺完善,好比BigInteger

    本地語言不是安全的,可能須要編寫膠合代碼,而且單調乏味和難以閱讀

    本地代碼中的一個Bug可能讓你痛不欲生

    謹慎地進行優化

    名人名言:

    不少計算機上的過失都被歸咎於效率(沒有必要達到的效率)

    計較效率上的小小的得失,不成熟的優化纔是一切問題的根源

    在優化方面咱們應該遵照兩條規則:第一,不要進行優化 第二,沒有絕對清晰的優化方案以前,請不要進行優化

    advise!

    API的設計對性能的影響是很是實際的,使用哪些性能優化過的API特別重要,保證接口的靈活性也特別重要

    不要費力的編寫快速的程序,盡力編寫好的程序,速度天然而然就隨之而來

    藉助性能方面的工具是不錯的手段

    遵照廣泛接受的命名慣例

    字面慣例的例子

    標示符類型 例子
    com.xiaoyu.util, org.xiaoyu.dao.impl
    類或者接口 HttpServlet, AsyncTask(單詞首字母大寫)
    方法或者域 toString, equal, isUpperCase(首單詞首字母小寫,以後單詞首字母大寫)
    常量域 IP_ADDR(所有大寫,單詞之間加下劃線)
    局部變量 stuNumber,mString(與方法命名相似)
    類型參數 T,E,V,K等等

    語法命名慣例

    標示符類型 例子
    名稱或名稱短語,Timer,BufferedWriter,ChessPiece
    接口 Collection,Comparator,Runnable,Iterable,Accessible
    註解 BindingAnnotation,Inject,ImplementedBy,Singleton
    方法或者域 動詞或動詞短語,append或drawImage,返回boolean is開頭 isEmpty,isEnabled

    方法返回非boolean對象的函數或屬性:speed(),color(),getTime(),getA+setA

    轉換對象類型:toType,toString,toArray
    返回view:asList
    返回和調用對象同值基本類型方法:typeValue、intValue
    靜態工廠的經常使用名稱:valueOf、getInstance、newInstance、getType、newType

    異常

    發揮異常優勢,提升程序的可讀性、可靠性和可維護性,介紹下異常的指導原則

    只針對異常的狀況才使用異常

    異常的錯誤做用:

    1. 模糊代碼的意圖
    2. 下降了它的性能
    3. 掩蓋Bug
    4. 增長調試過程的複雜性

    so,異常應該只用於異常的狀況,永遠不該該用於正常的控制流

    「狀態測試方法」 「可識別的返回值」

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

    Java 程序設計提供三種可拋出結構(throwable)

    1. 受檢異常(checked exception)
    2. 運行時異常(runtime exception)代表編程錯誤
    3. 錯誤(error)

    永遠不要定義Exception的子類,只會困擾API的用戶

    避免沒必要要的使用受檢異常

    catch 塊老是具備斷言失敗的特徵

    優先使用標準的異常

    標準意味着可被重用

    可被重用異常排行榜

    1. IllegalArgumentExcetion,當傳遞的參數不合適觸發--非法參數
    2. IllegalStateExcetion,當對象未被正確初始化,調用者企圖調用該對象觸發--非法狀態
    3. IndexOutOfBoundsException,參數致使越界—特殊的非法參數
    4. ConcurrentModificationException,被併發修改
    5. UnsupportedOperationException,對象不支持該操做
    6. ArithmeticException、NumberFormatException

    www.importnew.com/16725.html

    拋出和抽象相對應異常

    最讓人困惑的異常莫過於拋出的異常和執行任務沒有明顯的聯繫,每每由底層抽象拋出的異常?!

    如何避免?— 異常轉譯

    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        
    //異常鏈
            try{
                ... //Use lower-level bidding
            }catch(LowerLevelException cause){
                throw new HigherLevelException(cause);
            }
    複製代碼

    固然也不要濫用,能夠給低層傳遞參數前檢查高層方法的參數有效性,從而避免底層方法拋出異常

    而若是沒法避免底層異常,讓高層繞開這些異常,隔離!並記錄下來

    每一個方法拋出的異常都要有文檔

    在細節消息中包含能捕獲失敗的信息

    若是失敗的情形不容易重現,想要經過系統日誌分析會很是困難

    so,編寫代碼時把細節打印出來

    努力使失敗保持原子性

    1. 也就是在方法的一開始就拋出異常,避免執行下面的可能會修改的代碼
    2. 在對象狀態被修改以前發生異常
    3. 使用恢復代碼(recovery code),使對象回滾到操做開始以前的狀態
    4. 在臨時拷貝上執行操做,好比Collections.sort,即便排序失敗也保證列表原樣

    固然也不要刻意追求,錯就是錯了,另外API文檔應當清楚的指名對象將會處於什麼狀態

    不要忽略異常

    不考慮後果直接try catch是極其不負責任的行爲,起碼你解決不了,輸出些日誌出來也行

    併發

    同步訪問共享的可變數據

    Synchronized 同一時刻,只有一個線程能夠執行某一個方法或者代碼塊

    互斥的理解:當一個對象唄一個線程修改時,能夠阻止另外一個線程觀察到對象內部不一致的狀態(狀態一致性)

    boolean域的讀寫操做是原子的,讓第一個線程輪詢(開始false),經過第二個線程改變(false)代表第一個線程終止本身了

    i++不是原子操做

    不共享可變數據或者共享不可變數據以及非要共享可變數據就必須執行同步

    public class StopThread{
        private static boolean stopRequested;
        private static synchronized void requestStop(){
            stopRequested = true;
        }
        private static synchronized boolean stopRequested(){
            return stopRequested;
        }
        main(){
            Thread backgroudThread = new Thread(
                new Runnable(){
                    public void run(){
                        int i = 0;
                        while(!stopRequested())
                            i++;
                    }
                }
            );
            backgroundThread.start();
            TimeUnit.SECOND.sleep(1);
            requestStop();
        }
    }
    
    
    or ==>
    private static volatile boolean stopRequested;//就不須要寫同步方法了
    複製代碼

    避免過分同步

    過分同步可能會致使性能下降、死鎖、不肯定行爲

    死鎖緣由:它企圖鎖定某某,但它沒法得到該鎖

    分析StringBuffer內部同步問題

    分拆鎖、分離鎖、非阻塞併發控制

    executor和task優先於線程

    //代替new thread 的Executor Framework 三步曲
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.execute(runnable);
    executor.shutdown();
    
    //輕載的服務器,夠用不須要設置啥
    Executors.newCachedThreadPool
    //大負載產品,固定線程數目的線程池
    Executors.newFixedThreadPool 
    //最大限度的使用
    ThreadPoolExecutor
    
    複製代碼

    Executor Famework 所作的工做就是執行

    Collections Framework 所作的工做就是彙集

    使用ScheduledThreadPoolExecutor代替Timer,更靈活。Timer只用一個線程,面對長期任務影響準確性,一旦線程拋出未被捕獲的異常,timer就會中止。而executor支持多個線程,並可以優雅的從拋出的未受檢異常任務中恢復。

    併發工具優先於wait和notify

    應該用更高級的併發工具來代替 wait和notify(手動太困難)

    併發java.util.concurrent:Executor Framework、Concurrent Collection、Synchronizer

    併發集合:ConcurrentMap、BlockingQueue(生產者和消費者隊列)

    同步器:CountDownLatch/CyclicBarrier—倒計時鎖存器、Semaphore

    System.nanoTime更加準確更加精確,它不受系統的時鐘的調整影響(System.currentTimeMills)

    線程安全性的文檔化

    線程安全的級別:

    • 不可變 -- String、Long、BigInteger
    • 無條件線程安全 -- 已經內部處理無需關心,Random、ConcurrentHashMap
    • 有條件線程安全 -- Collections.synchronized包裝返回的集合,迭代器須要外部同步
    • 非線程安全 -- ArrayList、HashMap
    • 線程對立 -- 不考慮

    私有鎖,把鎖對象封裝在它所同步的對象中(private + final)適用於無條件線程安全

    總之,有條件的線程安全必須寫文檔指名了「哪一個方法調用序列須要外部同步,而且得到哪把鎖」

    慎用延遲初始化

    雙層檢查 :減小延遲初始化的性能開銷,再加上volatile

    不要依賴線程調度器

    不健壯、不可移植性

    避免使用線程組

    把堆棧軌跡定向一個特定域應用程序的日誌中Thread.setUncaughtExceptionHandler

    序列化

    將一個對象編碼成一個字節流,即序列化;相反,即爲反序列化。

    序列化代理模式

    謹慎地實現Serializable接口

    一個類被序列化的直接開銷很是低,可是後期的長期開銷確是實實在在的,一旦一個類被髮布,大大下降這個類實現的靈活性。

    舊版原本序列化一個類,在用新版本反序列化,每每會致使程序的失敗。

    UID — 序列化版本serialVersionUID,若是你沒有聲明顯式的序列化UID,那麼一旦類發生改變,那麼自動生成的UID也會發生變化,兼容性遭到破壞,運行時致使InvalidClassException異常

    反序列化做爲隱藏的構造器,具有和其餘構造器相同特色,很容易出現Bug和安全漏洞

    隨着類的新版本發佈,相關測試負擔加劇(確保序列化和反序列化過程成功)

    哪些類須要序列化(Date BigInteger、Throwable—異常從服務隊傳客戶端、Component—GUI的發送保持恢復、HttpServlet—回話狀態可緩存、存在目的爲了融於這種類型的框架)

    哪些類不該該實現序列化(thread pool、爲繼承設計的類+用戶接口—爲擴展存在的,否則會揹負沉重負擔)

    ReadObjectNoData

    內部類不因該是實現序列化

    考慮使用自定義的序列化形式

    transient表示的變量默認不序列化(瞬時),writeObject和readObject表示具體的序列化形式

    @serial標籤

    StringList

    保護性地編寫readObject方法

    readObject方法至關於公有構造器,如同其餘構造器同樣須要檢查參數有效性,必要時對參數進行保護性拷貝

    對於實例控制,枚舉類型優先於readResolve

    一個Singleton若是實現Serializable的話,就再也不是單例了,除非在readResolve作文章

    考慮用序列化代替序列化實例

    當不能在客戶端擴展類編寫readObject或者writeObject時候,考慮使用序列化代理模式

    讀後感 ?

    瞭解規則,看到背後,打破規則,創造新規則

相關文章
相關標籤/搜索