effecitveJava讀書筆記

1.創(chuàng)建與銷毀對(duì)象

1.1考慮用靜態(tài)工廠方法代替構(gòu)造器

這里的靜態(tài)工廠不是我們說設(shè)計(jì)模式中靜態(tài)工廠方法模式;可以理解為對(duì)外提供的一種服務(wù),不過是設(shè)置為靜態(tài)的莺治,方便我們調(diào)用
用靜態(tài)工廠方法代替構(gòu)造器的優(yōu)點(diǎn):

  • 有名稱耕漱,可以讓調(diào)用者更加明確會(huì)產(chǎn)生什么類型的對(duì)象
  • 結(jié)合final關(guān)鍵字,緩存等手段羊异,可以每次都返回同一個(gè)對(duì)象,這樣有助于提高性能
  • 可以返回原返回類型的各種任何子類型對(duì)象:例如Collections Framework API(詳見第52條)
  • 利用服務(wù)提供者框架(有服務(wù)接口彤断,服務(wù)提供者野舶,服務(wù)調(diào)用者三個(gè)角色)
    比如說:JDBC中,Connection就是服務(wù)接口宰衙,DriverManager.registerDriver是服務(wù)提供者API筒愚,DriverManager.getConnection(),是服務(wù)訪問API
/**
 * 提供服務(wù)
 * @author lanyangjia
 * @date 2019年4月1日21:29:03
 *
 */
public interface Provider {
    Service newService();
}

/**
 * 服務(wù)注冊(cè)接口
 * @author lanyangjia
 * @date 2019年4月1日21:29:11
 *
 */
public interface Service {

    void provideService();
}


/**
 * 注冊(cè)服務(wù)
 * @author lanyangjia
 * @date 2019年4月1日21:30:29
 *
 */
public class Services {
    //私有化構(gòu)造函數(shù)
    private Services(){}

    //服務(wù)的集合
    public static final Map<String,Provider> providers = new ConcurrentHashMap<>();
    //默認(rèn)服務(wù)提供者的名稱
    public static final String DEFAULT_PROVIDER_NAME = "<def>";

    //注冊(cè)服務(wù)提供者注冊(cè)的默認(rèn)API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME,p);
    }

    //提供參數(shù)的服務(wù)注冊(cè)API
    public static void registerProvider(String name, Provider p) {
        providers.put(name,p);
    }


    /**調(diào)用服務(wù)**/
    //調(diào)用Service的API
    public static Service newInstance() throws IllegalAccessException {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }

    //帶參數(shù)調(diào)用Service的API
    public static Service newInstance(String name) throws IllegalAccessException {
        Provider p = providers.get(name);
        if(p == null) {
            throw new IllegalAccessException("沒有這個(gè)服務(wù)的提供者" + name);
        }
        return p.newService();
    }

}

  • 在創(chuàng)建參數(shù)化類型實(shí)例的時(shí)候菩浙,可以讓代碼更加簡潔
    比如:Map中,給我們提供了一個(gè)靜態(tài)工廠
Map<String,List<String>> m = new HashMap<String,List<String>>
//優(yōu)化
Map<String,List<String>> m = HashMap.newInstance();//一種思路而已巢掺,事實(shí)上HashMap并沒有這個(gè)方法

這個(gè)思路句伶,可以讓我們寫在工具類中,或者放在參數(shù)化的類中(需要很長的參數(shù)化)陆淀,這樣看起來更優(yōu)雅美觀
缺點(diǎn)

  • 由于我們要私有化構(gòu)造方法考余,因此無法被繼承,但是我們可以用裝飾器模式等轧苫。減少繼承
  • 與其他靜態(tài)方法沒有區(qū)別
    常用的命名方式
  • valueOf:值是一樣的楚堤,轉(zhuǎn)換類型
  • of:同上
  • getInstance:返回的實(shí)例是通過方法的參數(shù)來確定的,如果是單例則返回同一個(gè)實(shí)例
  • newInstance: 返回不同的實(shí)例
  • getType:返回工廠方法的類型
  • newType:同上
    小結(jié)
    共有構(gòu)造方法和靜態(tài)工廠都有自己的優(yōu)點(diǎn)含懊,靜態(tài)工廠通常更適合身冬,因此我們?cè)诰帉懝ぞ哳惖臅r(shí)候或者需要參數(shù)化實(shí)例化一個(gè)類的時(shí)候要考慮靜態(tài)工廠。

1.2遇到多個(gè)構(gòu)造器參數(shù)式要考慮用構(gòu)建器(構(gòu)造器模式)

多個(gè)構(gòu)造方法岔乔,在這種模式下酥筝,可以提供第一個(gè)只有必要的參數(shù)構(gòu)造器,第二個(gè)構(gòu)造器有一個(gè)可選參數(shù)雏门,第三個(gè)有兩個(gè)可選參數(shù)嘿歌,以此類推,最后一個(gè)構(gòu)造器包含所有可選參數(shù)茁影。這種方式的話宙帝,容易會(huì)造成錯(cuò)誤,如果有相同的變量類型募闲,有時(shí)候會(huì)犯錯(cuò)~

public class NutritionFacts {
    /**必需的**/
    private final int servingSize;
    /**每一個(gè)容器**/
    private final int servings;
    /**操作**/
    private final int calories;
    /**g**/
    private final int fat;
    /**mg**/
    private final int sodium;
    /**g**/
    private final int carbohydrate;

    public NutritionFacts(int servingSize,int servings) {
        this(servingSize,servings,0);
    }

    public NutritionFacts(int servingSize,int servings,int calories) {
        this(servingSize,servings,calories,0);
    }

    public NutritionFacts(int servingSize,int servings,int calories,int fat) {
        this(servingSize,servings,calories,fat,0);
    }

    public NutritionFacts(int servingSize,int servings,int calories,int fat,int sodium) {
        this(servingSize,servings,calories,fat,sodium,0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}

上面的代碼可以看到我們自己看都有點(diǎn)累步脓,寫起來也要很注意邏輯的調(diào)用,客戶端調(diào)用我們的API的時(shí)候也非常難受浩螺。
還有一種優(yōu)化方式沪编,就是用JavaBeans的set方法去優(yōu)化,setXXXX年扩。這種,但是如果用這種模式的話访圃,難以保證類是不可變的厨幻,也就需要想出額外的方法去確保他是線程安全的。對(duì)象的一致性也不能得到很好的保證腿时。(是指如果別的地方修改了這個(gè)類的一些信息况脆,如果你某一個(gè)地方也用了這個(gè)類,然后會(huì)導(dǎo)致意想不到的錯(cuò)誤批糟?)


優(yōu)雅的方式:使用Builder模式格了,可以讓我們客戶端(指別人調(diào)用你的API)調(diào)用的時(shí)候具有很好地可讀性。不直接生成對(duì)象徽鼎,而是讓客戶端直接一些必要的參數(shù)盛末,得到一個(gè)builder對(duì)象弹惦,然后就類似setter那樣來設(shè)置每一個(gè)相關(guān)的可選參數(shù)。最后悄但,客戶端調(diào)用無參的builder來生成不可變的對(duì)象棠隐,減少不必要的內(nèi)存開支。Demo如下:

package secondChapter;

/**
 * Builder模式下構(gòu)建對(duì)象檐嚣,在需要很多構(gòu)造函數(shù)的時(shí)候助泽,可以考慮優(yōu)化成這樣
 * @author lanyangjia
 * @date 2019年4月2日22:17:57
 */
public class NutritionFacts {
    /**必需的**/
    private final int servingSize;
    /**每一個(gè)容器**/
    private final int servings;
    /**操作**/
    private final int calories;
    /**g**/
    private final int fat;
    /**mg**/
    private final int sodium;
    /**g**/
    private final int carbohydrate;

    //靜態(tài)成員類
    public static class Builder {
        //必須的參數(shù)
        private final int servingSize;
        private final int servings;
        private  int calories = 0;
        private  int fat = 0;
        private  int sodium = 0;
        private  int carbohydrate = 0;
        public Builder(int servingSize, int servings) {
            this.servings  = servings;
            this.servingSize = servingSize;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        //返回NutritionFacts對(duì)象
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        carbohydrate = builder.carbohydrate;
        fat = builder.fat;
        sodium = builder.sodium;
    }

    public static void main(String[] args) {
        NutritionFacts nutritionFacts = new NutritionFacts.Builder(240,8)
                .calories(100).sodium(35).carbohydrate(27).build();
    }
}

builder就像一個(gè)構(gòu)造器一樣,會(huì)將參數(shù)拷貝到真正的對(duì)象中嚎京,在對(duì)象域中對(duì)參數(shù)進(jìn)行校驗(yàn)(第39條嗡贺?);如果違反了某些約束鞍帝,則拋出IllegalArgumentException诫睬。Builder模式的好處在于,可以傳遞多個(gè)可變的參數(shù)膜眠。如果想創(chuàng)建不同類型的Builder岩臣,則可以聲明一個(gè)接口

/**
 * builder泛型接口
 * @param <T>
 */
public interface Builder<T> {
    public T build();
}

帶有Builder實(shí)例的方法通常利用有限制的通配符類型來約束構(gòu)建起的類型參數(shù)。例如

Tree builderTree(Builder <? extends Node> nodeBuilder){............}

Builder看上去很不錯(cuò)宵膨,但是自己寫挺麻煩的架谎,為了創(chuàng)建對(duì)象要先創(chuàng)建他的構(gòu)造器。雖然創(chuàng)建構(gòu)造器的開銷在實(shí)踐中可能微不足道辟躏。但是有時(shí)候?qū)懙臅r(shí)候代碼會(huì)很長谷扣,因此只有在很多參數(shù)的時(shí)候才使用。比如說:有4個(gè)以上的參數(shù)的時(shí)候捎琐,可以考慮用builder模式会涎。如果一開始就是用構(gòu)造器或者靜態(tài)工廠的話,以后我們?nèi)绻砑拥膮?shù)越來越多的時(shí)候瑞凑,構(gòu)造器的話非常難以維護(hù)末秃,看上去也不舒服。
小結(jié)
如果類的構(gòu)造器或者靜態(tài)工廠中具有逗哥參數(shù)籽御,那么設(shè)計(jì)這種類的時(shí)候,Builder模式就是不錯(cuò)的選擇练慕,特別是大多數(shù)參數(shù)是可選的時(shí)候。使用Builder模式的客戶端代碼更易于閱讀和編寫技掏。

1.3 使用私有構(gòu)造器或者枚舉類型實(shí)現(xiàn)單例模式

單例模式铃将,大家都應(yīng)該不會(huì)很陌生,他是只實(shí)例化一次的類
餓漢式:

public class Singleton {
    //1.將構(gòu)造方法設(shè)為私有哑梳,不允許外部直接創(chuàng)建對(duì)象
    private Singleton(){

    }
    //2.創(chuàng)建一個(gè)實(shí)例
    private static Singleton instance = new Singleton();

    //3提供一個(gè)用于獲取實(shí)例的方法

    public static Singleton getInstance(){
        return instance;
    }
}

懶漢式:

public class Singleton2 {
    //將構(gòu)造方法私有化
    private Singleton2(){

    }
    //聲明類的唯一實(shí)例
    private static Singleton2 instance;
    //提供一個(gè)對(duì)外獲取實(shí)例的方法
    public static Singleton2 getSingleton2() {
        if(instance == null)
        instance = new Singleton2();
        return instance;
    }


}

1.4通過私有構(gòu)造器來強(qiáng)化不可實(shí)例化的能力

我們一般自己編寫工具類的時(shí)候劲阎,都會(huì)用static來修飾方法,這樣用起來方便鸠真,但是這樣的工具類一般是不用實(shí)例化的悯仙,所以我們應(yīng)該把構(gòu)造方法私有化

public class Test {
  private UtilityClass() {
    throw new AssertionError();
  }
}

這種設(shè)計(jì)的方法的缺點(diǎn)就是龄毡,是的一個(gè)類不可以被子類化,所有的構(gòu)造器必須顯示或者隱式地調(diào)用超類的構(gòu)造器雁比,但是在這種模式下稚虎,顯然是不行的。

1.5 避免創(chuàng)建不必要的對(duì)象

一般來說偎捎,可以重用對(duì)象的盡量重用對(duì)象蠢终,這樣可以減少不必要的開銷。
比如說:
String s = new String("test");
"test"本身就是一個(gè)對(duì)象茴她,如果在循環(huán)中或者頻繁調(diào)用的方法中寻拂,這樣就會(huì)創(chuàng)建成千上萬的String實(shí)例。
改進(jìn)后的版本是
String s = "test";
上面說的避免創(chuàng)建不必要的對(duì)象丈牢,并不是說盡可能地避免創(chuàng)建對(duì)象祭钉。相反,由于小對(duì)象創(chuàng)建和回收動(dòng)作是非臣号妫快的慌核,特別是在JVM優(yōu)化到現(xiàn)在的層面。通過創(chuàng)建附加的對(duì)象申尼,提升程序的功能性垮卓,簡潔性和功能性,對(duì)于開發(fā)來說师幕,也是非常有意義的粟按。
正確的來說,應(yīng)該是可以重用對(duì)象的時(shí)候霹粥,不要?jiǎng)?chuàng)建新的對(duì)象灭将。
舉例:

public class Person {

    private final Date birthDate;
//    private static final Date BOOM_START;
//    private static final Date BOOM_END;
    public Person(Date date) {
        this.birthDate = date;
    }

    public boolean isBabyBoomer() {
        Calendar gtmCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gtmCal.set(1946,Calendar.JANUARY,1,0,0,0);
        Date boomStart = gtmCal.getTime();
        gtmCal.set(1965,Calendar.JANUARY,1,0,0,0);
        Date boomEnd = gtmCal.getTime();
        return birthDate.compareTo(boomStart) >= 0 &&
                birthDate.compareTo(boomEnd) < 0;
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        Person person = new Person(new Date());
        for(int i=0; i<1000000; i++) {
            person.isBabyBoomer();
        }
        System.out.println("時(shí)間花費(fèi)為" + (System.currentTimeMillis() - start));
    }
}

這個(gè)程序每次都會(huì)創(chuàng)建Calendar,一個(gè)TimeZone,兩個(gè)Date實(shí)例.花費(fèi)的時(shí)間為


image.png

改進(jìn)版:將Calendar后控,TimeZone庙曙,Date實(shí)例化一次,而不是每次調(diào)用方法的時(shí)候創(chuàng)建浩淘,優(yōu)化如下

public class Person {

    private final Date birthDate;
    private static final Date BOOM_STAR;
    private static final Date BOOM_END;
    public Person(Date date) {
        this.birthDate = date;
    }

    static {
        Calendar gtmCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gtmCal.set(1946,Calendar.JANUARY,1,0,0,0);
        BOOM_STAR= gtmCal.getTime();
        gtmCal.set(1965,Calendar.JANUARY,1,0,0,0);
        BOOM_END = gtmCal.getTime();
    }
    public boolean isBabyBoomer() {

        return birthDate.compareTo(BOOM_STAR) >= 0 &&
                birthDate.compareTo(BOOM_END) < 0;
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        Person person = new Person(new Date());
        for(int i=0; i<1000000; i++) {
            person.isBabyBoomer();
        }
        System.out.println("時(shí)間花費(fèi)為" + (System.currentTimeMillis() - start));
    }
}
image.png

優(yōu)化的效率上去了~
tisp:
Java中有自動(dòng)裝箱拆箱的功能捌朴。相同條件下Long的花費(fèi)時(shí)間比long更高。因此平時(shí)編碼要注意哦馋袜!

1.6 消除過期對(duì)象的引用

觀察下面的程序

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();
        }
        return elements[--size];
    }


    /**
     * 擴(kuò)容
     */
    public void ensureCapacity() {
        if(elements.length == size) {
            elements = Arrays.copyOf(elements,2 * size +  1);

        }
    }

}

上面的程序我們無論怎樣去測試應(yīng)該都是沒有問題的。但是還是會(huì)有隱患舶斧,隨著垃圾回收器活動(dòng)的增加欣鳖,或者隨著內(nèi)存占用的不斷增加,程序性能會(huì)變低茴厉。極端情況下會(huì)出現(xiàn)OOM
會(huì)出現(xiàn)的原因是泽台,一個(gè)棧先增長什荣,然后再收縮,那么從棧中彈出的對(duì)象將不會(huì)當(dāng)做垃圾回收怀酷,即便是這些元素已經(jīng)不在棧中了稻爬。。這是因?yàn)橥梢溃瑮?nèi)部維護(hù)著對(duì)這些對(duì)象的過期引用(指永遠(yuǎn)不會(huì)解除對(duì)這個(gè)對(duì)象的引用)桅锄。
如何解決呢?我們只要告訴JVM样眠,我們不再引用這個(gè)對(duì)象即可友瘤。

 public Object pop() {
        if(size == 0) {
            throw new EmptyStackException();
        }
        elements[size] = null;
        return elements[--size];
        
    }

然而,我們?cè)陂_發(fā)的時(shí)候不需要每時(shí)每刻都將不用的對(duì)象置為null檐束,清空對(duì)象引用應(yīng)該是一種例外辫秧,而不是一種規(guī)范行為。如果是類管理自己內(nèi)存的時(shí)候被丧,我們就要警惕了盟戏。像Stack類,自己會(huì)管理內(nèi)存甥桂,存儲(chǔ)池中包含了elements數(shù)組(對(duì)象引用單元柿究,而不是對(duì)象本身),垃圾回收器是不知道數(shù)組中那些是可用的格嘁,哪些是不可用的笛求。因此它誤以為里面的所有引用都是有效的,這個(gè)時(shí)候就需要我們告訴垃圾回收器糕簿,那些是不需要的探入。
(西面兩個(gè)還沒懂,懂诗,可能得了解下JVM知識(shí))

  • 內(nèi)存泄露的另外一個(gè)常見來源是緩存蜂嗽。
    比如說我們放了對(duì)象在緩存中,但是我們可能好久沒用了殃恒,但是緩存中還是會(huì)存在的植旧。那么如何解決呢?
    緩存外离唐,保存對(duì)某個(gè)項(xiàng)的鍵的引用病附,這個(gè)項(xiàng)就有意義,可以用WeakHashMap代表緩存亥鬓;當(dāng)緩存中的項(xiàng)過期后完沪,里面的值會(huì)自動(dòng)刪除。記住覆积!緩存中是否存在是由該鍵的外部引用決定的
  • 一個(gè)是監(jiān)聽器和其他回調(diào)

1.7 避免使用終結(jié)方法

程序中不要使用finalizer方法听皿。非常危險(xiǎn)~

2.對(duì)于所有對(duì)象都通用的方法

2.1 覆蓋equals原則

  • 使用 == 操作符檢查“參數(shù)是否為這個(gè)對(duì)象的引用”;
  • 使用instanceof操作符檢查類型是否爭瓤淼怠尉姨;
  • 把參數(shù)轉(zhuǎn)為正確的類型
  • 對(duì)于該類中的關(guān)鍵域,檢查參數(shù)中的域是否與該對(duì)象的中的域相等吗冤;
  • 覆蓋equals方法的時(shí)候又厉,總要覆蓋hashCode(可以結(jié)合HashMap,TreeMap,HashSet等散列表)
  • 不要企圖讓equals方法過于智能;
  • 不要將equals中的Objece對(duì)象轉(zhuǎn)換為其他類型

2.2覆蓋toString方法

建議所有的子類都覆蓋toString方法欣孤,這對(duì)于我們程序的調(diào)試或者看一些關(guān)鍵信息很有作用馋没。

2.3 謹(jǐn)慎使用clone方法

這個(gè)方法不常用。現(xiàn)在不知道有什么用降传。篷朵。。先不做闡述

2.4考慮實(shí)現(xiàn)Comparable接口

一旦類實(shí)現(xiàn)了Compareable接口婆排,他就可以跟許多泛型算法以及依賴該接口的集合實(shí)現(xiàn)就行協(xié)作声旺。(考慮比較排序的時(shí)候可以實(shí)現(xiàn)這個(gè)接口)

3.類和接口

3.1使類和成員的可訪問性盡可能低

修飾類,方法段只,屬性等有四種訪問級(jí)別腮猖;private,default,protectd,public。在我們?cè)O(shè)計(jì)類的時(shí)候有以下的準(zhǔn)則

  • 實(shí)例域決不能是公有的赞枕。如果域是非final的澈缺,或者是一個(gè)指向可變對(duì)象的final引用,那么一旦使這個(gè)域變成私有的炕婶,那么就放棄了限制能力姐赡。。因?yàn)槟啵m然本身是不可以被修改的项滑,但是所引用的對(duì)象是可以被修改的。這是線程不安全的涯贞,其他也是可以可以進(jìn)行修改的枪狂。因此,還是要把域的可訪問性進(jìn)行限制
  • 在仔細(xì)設(shè)計(jì)一個(gè)最小的公有API的時(shí)候宋渔,應(yīng)該防止把任何散亂的類州疾,接口或者成員變成API的一部分。盡可能地解耦
  • 除了公有靜態(tài)final域的特殊情形外皇拣,公有的類都不應(yīng)該包含公有域严蓖,并且要確保公有靜態(tài)final域所引用的對(duì)象都是不可變的。

3.2在公有類中使用訪問方法而非公有域

這是一個(gè)很常見設(shè)計(jì)類的時(shí)候的規(guī)則,就是一般的域都設(shè)為私有的谈飒,然后對(duì)外提供訪問和設(shè)置的API。但是有時(shí)候态蒂,需要用包級(jí)別或者私有的嵌套類來暴露域杭措,無論這個(gè)類是可變的還是不可變的。

3.3使可變性最小化

不可變類只是在其實(shí)例化不能被修改的類钾恢。每個(gè)實(shí)例中包含的信息都必須在創(chuàng)建該實(shí)例的時(shí)候就提供手素,并在對(duì)象的整個(gè)生命周期內(nèi)固定不變。Java中給我們提供的不可變的類有瘩蚪,String(被final修飾泉懦,不可以被子類化),基本類型的包裝類疹瘦,BigInteger和BigDecimal崩哩。不可變的類比可變的類更加容易設(shè)計(jì),實(shí)現(xiàn)言沐,使用邓嘹。有下面五條規(guī)則:

  • 不要提供任何會(huì)修改對(duì)象的狀態(tài)的方法
  • 保證類不會(huì)被擴(kuò)展
  • 使所有的域都是final 的
  • 使所有的域都是私有的
  • 確保對(duì)于任何可變組件的互斥訪問(也就是說,不提供給客戶端任何API獲取該實(shí)例)险胰;并且汹押,永遠(yuǎn)不要用客戶端提供的對(duì)象引用來初始化這樣的域,因?yàn)橛蛑赶虻囊脤?duì)象是可變的起便,有被修改的風(fēng)險(xiǎn)棚贾。在構(gòu)造器,訪問方法和readObject方法中使用保護(hù)性拷貝
    不可變對(duì)象的優(yōu)點(diǎn)和缺點(diǎn)
    優(yōu)點(diǎn):
  • 不可變對(duì)象本質(zhì)上是線程安全的榆综,不要求同步
  • 可以共享不可變對(duì)象妙痹,甚至可以共享他們的內(nèi)部信息
  • 為其他對(duì)象提供樂然大量的構(gòu)件
    缺點(diǎn):
  • 對(duì)于每一個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。
    什么時(shí)候使用不可變對(duì)象奖年?
    1.一些頻繁被調(diào)用的域细诸,計(jì)算復(fù)雜的可以可以用final修飾(String;延遲初始化)
    2.如果不可變的類實(shí)現(xiàn)序列化接口陋守,就必須提供一個(gè)顯示的readObject或者readResolve方法震贵,或者使用ObjectOutputStream.writeUnshared和ObjectInputStream.readUnshared。
    3.不要為每一個(gè)get方法編寫一個(gè)set方法水评。除非有很好的理由讓類變成可變的類猩系,否則就應(yīng)該是不可變得。
    4.構(gòu)造器應(yīng)該創(chuàng)建完全初始化的對(duì)象(為每一個(gè)域都賦值)中燥;不要在構(gòu)造器或者靜態(tài)工廠之外再提供共有的初始化方法寇甸。同時(shí),不要提供重新初始化的方法(這個(gè)應(yīng)該沒什么人這么做);
    舉例:TimerTask類

3.4復(fù)合優(yōu)于繼承

我們學(xué)Java的時(shí)候知道繼承是面向?qū)ο蟮娜筇卣髦荒妹梗乙彩菍?shí)現(xiàn)代碼復(fù)用的重要手段吟秩。但它并非永遠(yuǎn)是完成這項(xiàng)工作的最佳工具。

  • 與方法調(diào)用不同的是绽淘,繼承打破了封裝性涵防。因?yàn)樽宇愐蕾嚻涑愔刑囟üδ艿膶?shí)現(xiàn)細(xì)節(jié)。超類的實(shí)現(xiàn)可能隨著版本的更替沪铭,而不斷改變壮池,這個(gè)時(shí)候由于子類是繼承自超類的,這樣可能會(huì)對(duì)子類造成破壞杀怠。因此椰憋,子類必須跟著其超類的更新而演化,除非超類是專門為了擴(kuò)展而設(shè)計(jì)的赔退,并且具有對(duì)應(yīng)的說明文檔
  • 復(fù)用:在新的類中增加一個(gè)私有域橙依,它引用現(xiàn)有類的一個(gè)實(shí)例。這種設(shè)計(jì)叫做“復(fù)合”硕旗;
/**
 * 實(shí)現(xiàn)Set接口
 * @author lanyangjia
 * @param <E> 參數(shù)類型
 */
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
    public ForwardingSet(Set<E> s) {
        this.s = s;
    }

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

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

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

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

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

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


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

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

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

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

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

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

    @Override
    public void clear() {
        s.clear();
    }
}
----------------------------------分割線-----------------------------------------------------------------------
import java.util.Collection;
import java.util.Set;

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

觀察以上代碼:本質(zhì)上來講InstrumentedSet類實(shí)現(xiàn)了Set接口票编,并且擁有單個(gè)構(gòu)造器。這個(gè)把一個(gè)Set轉(zhuǎn)變成了另外一個(gè)Set卵渴,同時(shí)增加了計(jì)數(shù)功能慧域。這樣的設(shè)計(jì)帶來了很多好處,可以包裝任何Set實(shí)現(xiàn)浪读,并且可以結(jié)合任何先前存在的構(gòu)造器一起工作昔榴,例如:

   Set<Date> s = new InstrumentedSet<Date>(new TreeSet<Date>());
        Set<E> s2 = new InstrumentedSet<E>(new HashSet<>());

甚至可以代替原來沒有計(jì)數(shù)功能的Set實(shí)例
因?yàn)槊恳粋€(gè)InstrumentedSet都把另外一個(gè)Set實(shí)例包裝起來,所以InstrumentedSet也叫做包裝類碘橘。包裝類幾乎沒什么缺點(diǎn)互订,唯一的缺點(diǎn)就是不適用于有回調(diào)功能的情況。在回調(diào)功能中痘拆,對(duì)象把自身的引用傳遞給其他對(duì)象仰禽,用于后續(xù)的調(diào)用(“回調(diào)”)。因?yàn)楸话b起來的對(duì)象并不知道他外面的包裝對(duì)象纺蛆,所以它傳遞一個(gè)指向自身的引用吐葵,回調(diào)的時(shí)候避免了外面的包裝對(duì)象。
從上面可以看出:
1.只有當(dāng)子類真正是超類的子類型的時(shí)候桥氏,才適合用繼承温峭。換句話說,對(duì)于兩個(gè)類A,B只有兩者之間確實(shí)存在is-a關(guān)系的時(shí)候字支,B才應(yīng)該擴(kuò)展A凤藏。每次要用繼承的時(shí)候要先問問自己奸忽,B是不是也是A的一種呢?如果不是揖庄,那么一般情況下栗菜,B應(yīng)該包含A的一個(gè)私有實(shí)例,并且暴露一個(gè)較小的蹄梢,簡單的API
2.超類中的API是否有缺陷呢苛萎?如果有,就可以利用復(fù)合來設(shè)計(jì)新的API來隱藏這些缺陷检号。
總結(jié):

  • 繼承功能非常強(qiáng)大, 但是違背了封裝的原則蛙酪。只有當(dāng)超類齐苛,子類確實(shí)存在is-a關(guān)系的時(shí)候才考慮用繼承。
  • 如果子類和超類處在不同的包中桂塞,并且超類不是為了繼承而設(shè)計(jì)的凹蜂,那么繼承將會(huì)導(dǎo)致脆弱性。
  • 可以利用復(fù)合來代碼繼承阁危,尤其是在存在適當(dāng)?shù)慕涌?/strong>可以實(shí)現(xiàn)包裝類的時(shí)候玛痊。包裝類不僅比子類更加健壯,而且功能更加強(qiáng)大狂打!

3.5使用繼承的時(shí)候要注意的問題

  • 使用繼承的時(shí)候要注意寫好文檔擂煞,那些是可覆蓋的(文檔必須指明什么該方法或者構(gòu)造器調(diào)用了哪些可覆蓋的方法,調(diào)用的順序趴乡,又會(huì)如何影響后面的結(jié)果)对省;覆蓋的要求是什么
  • 決不能在構(gòu)造器中調(diào)用可覆蓋的方法
  • 如果設(shè)計(jì)一個(gè)類中實(shí)現(xiàn)了Serizlizable接口,并且該類有一個(gè)readResolve或者writeReplace方法的晾捏,就必須使readResolve或者writeReplace成為受保護(hù)的方法蒿涎。

3.6接口優(yōu)于抽象類

為什么?

  • 現(xiàn)有的類容易被更新惦辛,以實(shí)現(xiàn)新的接口
  • 接口是定義mixin(混合類型)的理想選擇劳秋。例如:Comparable接口,可以允許不同實(shí)例進(jìn)行排序胖齐。
  • 接口允許我們構(gòu)造非層次結(jié)構(gòu)的關(guān)系玻淑。比如時(shí)候:水果包含了蘋果,這是一種層次結(jié)構(gòu)呀伙。而蘋果岁忘,香蕉他們是同級(jí)的沒有上下級(jí)之分。
    骨架
    通過結(jié)合抽象類和接口的優(yōu)點(diǎn)区匠,可以設(shè)計(jì)一個(gè)通用的骨架干像。比如JDK中我們熟悉的AbstractSet帅腌,AbstractList。麻汰。速客。。他們成為集合的骨架 五鲫。具體的如何實(shí)現(xiàn)可以看看JDK的源碼溺职。。書里說的demo語言確實(shí)有點(diǎn)拗口位喂。
    優(yōu)點(diǎn):
    演變比接口簡單浪耘,試想一下,如果我們單純實(shí)現(xiàn)了一個(gè)接口塑崖,然后你新增了一個(gè)方法七冲,這樣的話,你就不得不去讓實(shí)現(xiàn)了這個(gè)接口的類规婆,再一次覆蓋這些方法澜躺。如果使用抽象類的話,就可以直接在父類中增加你想增加的方法抒蚜,這樣擴(kuò)展會(huì)更加簡單
    總結(jié)
  • 接口的使用比抽象類相對(duì)來說更加廣泛掘鄙,但是接口的設(shè)計(jì)一定要非常謹(jǐn)慎,因?yàn)橐坏┰O(shè)計(jì)成接口了嗡髓。再想改接口的話工作量會(huì)變大
  • 如果我們需要演變更加靈活的時(shí)候選擇抽象類比接口更加合適
  • 每次設(shè)計(jì)完接口或者抽象類要進(jìn)行全面的測試操漠,確認(rèn)設(shè)計(jì)的接口或者實(shí)現(xiàn)類沒有BUG

3.7不要用接口定義常量

我們知道接口里面也可以定義常量,而且都是static final的饿这,但事實(shí)上很少人這么做颅夺,也不推薦這么做

3.8使用函數(shù)對(duì)象表示策略(策略模式)

策略表示你傳入不同的參數(shù),或者說你采用不同的策略會(huì)返回不同的結(jié)果蛹稍,比如說你傳入兩個(gè)參數(shù)吧黄,你采用加法策略返回的時(shí)候加法的結(jié)果嵌灰,采用除法策略會(huì)返回除法的結(jié)果藤乙。块差。哟旗。夺蛇。
我們?cè)谠O(shè)計(jì)策略類的時(shí)候怎诫,還需要定義一個(gè)策略接口

public interface Comparator<T> {
    public int compare(T t1,T t2);
}

class XXX implements Comparator{
////
}

具體的策略類凤跑,往往使用匿名類聲明确徙,demo如下

 String[] stringArray = new String[] {"a","b","c","d"};
        //每次使用會(huì)創(chuàng)建一個(gè)Comparator實(shí)例声功,可以考慮放在final里面
        Arrays.sort(stringArray, Comparator.comparingInt(String::length));

總結(jié):
1.當(dāng)我們實(shí)現(xiàn)策略模式的時(shí)候烦却,需要聲明一個(gè)接口來表示該策略;然后為每一個(gè)具體策略聲明一個(gè)實(shí)現(xiàn)了該接口的類
2.當(dāng)一個(gè)具體策略只被執(zhí)行一次的時(shí)候先巴,通常使用匿名類來聲明和實(shí)現(xiàn)這個(gè)具體策略類
3.當(dāng)一個(gè)具體策略設(shè)計(jì)重復(fù)使用的時(shí)候其爵,可以將類實(shí)現(xiàn)為私有的靜態(tài)成員類冒冬;并通過公有的靜態(tài)final域被導(dǎo)出;如下

public class Host {
    private static class StrLenCmp implements Comparator<String>, Serializable {
        @Override
        public int compare(String t1, String t2) {
            return 0;
        }
    }

    public static final Comparator<String> STRING_COMPARATOR = new StrLenCmp();
}

3.9優(yōu)先使用靜態(tài)成員類

我們知道內(nèi)部類主要4種,靜態(tài)成員類摩渺,非靜態(tài)成員類简烤,匿名類,局部類(很少用)摇幻。靜態(tài)成員類和非靜態(tài)成員類的區(qū)別就是關(guān)鍵字横侦,有沒有static,理論上來說:非靜態(tài)成員類在被創(chuàng)建的時(shí)候绰姻,它和外圍之間的實(shí)例之間的關(guān)系也就被構(gòu)建起來了枉侧。(這個(gè)很少用)。不過JDK中也有用到狂芋。比如Map中的keySet,entrySet和Values方法返回榨馁。。
如果靜態(tài)成員類不要求訪問外圍實(shí)例银酗,就要始終把static修飾符放在它的聲明中,如果省略了static徒像,那么每一個(gè)實(shí)例創(chuàng)建的時(shí)候都會(huì)額外包括一個(gè)指向外圍對(duì)象的引用黍特,造成不必要的開銷
總結(jié):
1.如果一個(gè)嵌套類需要在單個(gè)方法之外仍然可見的,或者它太長了锯蛀,不適合放在方法內(nèi)部灭衷,可以使用成員類
2.如果成員類每一個(gè)實(shí)例都需要一個(gè)指向外圍實(shí)例的引用,就要把成員類做成非靜態(tài)的旁涤;否則翔曲,做成靜態(tài)的
3.假設(shè)這個(gè)嵌套類屬于一個(gè)方法的內(nèi)部,如果你只需要在一個(gè)地方創(chuàng)建實(shí)例劈愚,可以把它做成匿名類瞳遍。

4.泛型

泛型是在編譯的時(shí)候檢查我們的集合類型是否正確,建議在開發(fā)的時(shí)候就確定好類型菌羽,而且不要和數(shù)組互相轉(zhuǎn)換掠械。因?yàn)閿?shù)組是運(yùn)行時(shí)類型安全,泛型是編譯的時(shí)候類型安全注祖,在運(yùn)行的時(shí)候會(huì)進(jìn)行泛型擦除猾蒂。
關(guān)于泛型的建議:

  • 優(yōu)先考慮泛型
  • 優(yōu)先考慮泛型方法
  • 優(yōu)先考慮用泛型方法(在開發(fā)中,如果我們能保證返回的類型是正確的是晨,則可以用@SuppressWarnings注解來標(biāo)識(shí))
  • 利用有限制通配符來提升API的靈活性:PECS:producer-extends,consumer-super

5.枚舉和注解

5.1 用枚舉代替Int常量

int類型如果我們不注意的話非常容易犯錯(cuò)肚菠,如以下代碼:

public staitc final int APPLE_FUJI = 0;
public static final int ORAGLE_NAVEL = 0;
int i = (APPLE_FUJI - ORAGLE_NAVEL) / ....

如果你將蘋果和橙子進(jìn)行比較,編譯器不會(huì)任何警告
優(yōu)化:

public enum Apple{FUJI,PIPPIN}
public enum Orange{NAVEL,TEMPLE}

枚舉是一種構(gòu)造方法私有化罩缴,域?yàn)閒inal的特殊類蚊逢。他們是單例的泛型化层扶。如果第一個(gè)參數(shù)聲明為Apple,那么我們后面只能從Apple里面取时捌。
其他更高級(jí)的enum用法怒医,尚未接觸,故跳過

5.2注解優(yōu)于命名模式

注解的出現(xiàn)奢讨,讓命名模式的缺點(diǎn)得以改善(讓我們?cè)谡_的位置稚叹,避免因?yàn)槠疱e(cuò)名而導(dǎo)致程序無法執(zhí)行)


//運(yùn)行的時(shí)候執(zhí)行
@Retention(RetentionPolicy.RUNTIME)
//在什么地方上聲明,方法拿诸,類扒袖,域?
@Target(ElementType.METHOD)
public @interface Test {
    
}
public class Sample {
    @Test
    public static void m1(){} //Test should pass

    public static void m2(){}

    @Test
    public static void m3(){ //Test should fail
        throw new RuntimeException("Boom");
    }

    public static void m4(){}

    @Test
    public void m5(){}

    public static void m6(){}

    @Test
    public static void m7() { //Test should fail
        throw new RuntimeException("Crash");
    }

    public static void m8(){}
}

上面會(huì)有兩項(xiàng)失敗亩码,一項(xiàng)會(huì)通過季率,另一項(xiàng)無效
如果想指定拋出的異常

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Exception> value();
}

總結(jié)
大多數(shù)程序員都不用定義注解類型,但是所有的程序員都應(yīng)該使用Java平臺(tái)提供的預(yù)定義的注解類型描沟。

6.方法

6.1對(duì)參數(shù)進(jìn)行有效的校驗(yàn)

有時(shí)候我們寫方法的時(shí)候飒泻,為了避免出現(xiàn)不必要的錯(cuò)誤,需要對(duì)參數(shù)進(jìn)行校驗(yàn)吏廉,讓方法很好地運(yùn)行

6.2謹(jǐn)慎設(shè)計(jì)方法簽名

  • 謹(jǐn)慎地選擇方法的名稱泞遗;
  • 不要過于追求便利的方法:每一個(gè)方法都應(yīng)該盡其所能。方法太多會(huì)使類難以學(xué)習(xí)和維護(hù)席覆。
  • 避免過長的參數(shù)列表史辙。最好控制在四個(gè)以內(nèi),如果過多了考慮用對(duì)象封裝起來佩伤。(如果參數(shù)過多聊倔,而且可選的時(shí)候,可以考慮用Builder模式來優(yōu)化生巡。)
  • 對(duì)于參數(shù)類型耙蔑,要優(yōu)先使用接口來接收;比如public void test(Map<String,Objecet>);這一個(gè)方法的話可以接收TreeMap孤荣,HashMap等子映射表
  • 對(duì)于要傳入boolean類型的纵潦,優(yōu)先使用兩個(gè)元素的枚舉類型。它使代碼更易于閱讀和編寫垃环。比如:
    你可能會(huì)有有個(gè)Thermometer類型邀层,它帶有一個(gè)靜態(tài)工廠方法,而這個(gè)靜態(tài)工廠方法的簽名需要傳入這個(gè)枚舉的值:
    public enum TemperatureScale{FAHRENHEIT,CELSIUS}
    Thermometer.newInstacne(TemperatureScale.CELSIUS)比Thermometer.newInstacne(TemperatureScale.true)更有用遂庄,而且以后可能會(huì)增加新的枚舉類型寥院。

6.3 慎用重載

舉個(gè)例子:

package seven;

import java.math.BigInteger;
import java.util.*;

public class CollectionClassifier {

    public static String classify(Set<?> s) {
        return "Set";
    }

    public static String classify(List<?> list) {
        return "List";
    }

    public static String classify(Collection<?> c) {
        return "Unknown Collection";
    }

    public static void main(String[] args) {
        Collection<?> []collection = {
                new HashSet<String>(),
                new ArrayList<BigInteger>()
        };

        for (Collection<?> objects : collection) {
            System.out.println(classify(objects));
        }
    }
}

image.png

輸出的結(jié)果都是Unknown Collection.因?yàn)橹剌d的話,編譯器的選擇是靜態(tài)的涛目。在運(yùn)行的時(shí)候編譯器已經(jīng)選擇好了運(yùn)行哪個(gè)方法
如何我想用重載秸谢,但是不想出現(xiàn)上面的情況呢凛澎?
1.永遠(yuǎn)不要導(dǎo)出兩個(gè)具有相同參數(shù)的數(shù)目的重載方法
2.使用別名,不要使用相同的名字估蹄。比如ObjectOutpuStream里面


image.png
  • 謹(jǐn)慎使用可變參數(shù)(幾乎在平時(shí)工作中沒用到過塑煎。。臭蚁。最铁。)

6.4返回零長度的數(shù)組或者集合,而不是null

比如:

private final List<Cheese> cheessList = ....
public Cheese[] getCheese() {

if(cheessList.size == 0) {
return null;
}
}

如果我們把一個(gè)返回值設(shè)為null垮兑,那么客戶端需要處理的情況就會(huì)很多冷尉,需要判斷你的返回值是否為空;很容易讓人犯錯(cuò)系枪,如果一個(gè)沒判空就容易造成空指針雀哨。。私爷∥砉祝或者如果前后端交互的話,前端就需要寫大量的校驗(yàn)代碼衬浑。
如何優(yōu)雅的避免呢捌浩?

public class Cheese {
    private List<Cheese> cheeseList = ....;

    private static final List<Cheese> returnResult = new ArrayList<>();
}

或者


    public List<Cheese> getCheeseList() {
        if(cheeseList.isEmpty()) {
            Collections.emptyList();
        }
        return new ArrayList<>();
    }

Collections.emptySet,emptyList,emptyMap方法提供的正式你所需要的。
總結(jié):
返回類型為數(shù)組或者為集合的方法嚎卫,沒理由返回null嘉栓;正確的做法是返回一個(gè)零長度的數(shù)組或者集合

6.5 為所有導(dǎo)出的API元素編寫文檔

我們?cè)陂_發(fā)中其實(shí)很少會(huì)導(dǎo)出API文檔宏榕,這些在jdk源碼中很重要拓诸,或者一些開源的框架很重要。在我們平時(shí)的開發(fā)中麻昼,自己寫注釋也很重要5熘А!81睹铡!叉抡!寫注釋有時(shí)候不是為了方便別人尔崔,也是為了方便別人!


image.png

7.通用程序設(shè)計(jì)

7.1將局部變量作用域最小化

簡而言之就是褥民,盡量不要使用全局變量季春,在方法內(nèi)部是用局部變量,使變量的作用域最小化

7.2for-each循環(huán)優(yōu)于傳統(tǒng)的for循環(huán)

for-each是一種很優(yōu)雅的遍歷方式消返,而且不容易犯錯(cuò)载弄。不過要注意耘拇,以下三種情況無法使用for-each:

  • 過濾:需要邊遍歷,邊刪除一個(gè)元素的時(shí)候宇攻,就需要使用顯示的迭代器
  • 轉(zhuǎn)換:如果需要在遍歷的過程中惫叛,取代部分或者全部的元素值,就需要使用列表的迭代器或者數(shù)組索引逞刷,以便設(shè)定元素的值
  • 平行迭代:如果需要并行地遍歷多個(gè)集合嘉涌,就需要顯示地控制迭代器或者索引變量,以便所有迭代器或者索引變量都可以得到同步前移

7.3了解和是使用類庫

這是一條有意思的建議亲桥。比如說我們?nèi)‰S機(jī)數(shù)的時(shí)候洛心,JDK已經(jīng)為我們提供了隨機(jī)Random類,我們不需要再絞盡腦汁取想怎么實(shí)現(xiàn)题篷,還有集合框架的一些工具類词身,都是JDK為我們封裝好的》叮總而言之法严,不要重復(fù)造輪子。在做一個(gè)需求的時(shí)候葫笼,先想想有沒有相關(guān)的工具類深啤,或者已經(jīng)實(shí)現(xiàn)好的類庫去供我們使用,這樣可以把精力放在程序上路星,而不是底層的細(xì)節(jié)上溯街。當(dāng)然如果你有空的話,可以看看底層一些細(xì)節(jié)洋丐,這樣可以看看別人優(yōu)秀的編碼方式呈昔,從而從側(cè)面提高自己

7.4如果需要精確答案,請(qǐng)避免使用float和double

float和double都是為了科學(xué)計(jì)算和工程計(jì)算而設(shè)計(jì)的友绝。它們執(zhí)行二進(jìn)制浮點(diǎn)運(yùn)算堤尾。但是它們不適合用于貨幣計(jì)算。正確的做法是使用BigDecimal迁客,int郭宝,或者long進(jìn)行貨幣計(jì)算。BigDecimal有兩個(gè)缺點(diǎn)掷漱,一個(gè)是不方便粘室,一個(gè)是運(yùn)行很慢〔贩叮可以考慮將元轉(zhuǎn)分衔统,用int或者long存儲(chǔ)。
如果性能非常關(guān)鍵,并且不介意自己記錄十進(jìn)制的小數(shù)點(diǎn)缰冤,就可以用int或者long犬缨。如果數(shù)組沒有超過9,就可以使用int棉浸,如果不超過18位怀薛,則可以使用long。如果數(shù)組可能超過18位迷郑,則需要用BigDecimal

7.5基本類型優(yōu)于裝箱基本類型

我們知道Java為我們提供了自動(dòng)的裝箱拆箱功能枝恋,這非常便利,但是有時(shí)候也會(huì)為我們帶來困擾嗡害;比如下面的程序:

public class Test {
    static Integer i;
    public static void main(String[] args) {
        if(i == 42) {
            System.out.println("xxxx");
        }
    }
}
image.png

因?yàn)镮nteger是一個(gè)對(duì)象焚碌,具有額外的非功能值,null霸妹。因此會(huì)報(bào)錯(cuò)
再來一個(gè)例子

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

如果量具有相同的值new Integer(42)和new Integer(42)十电。這兩個(gè)相同的,為什么會(huì)輸出-1呢叹螟。因?yàn)榈忍?hào)是比較地址的鹃骂,他們的地址不是一致的,返回false罢绽,比較器就會(huì)錯(cuò)誤地返回1畏线。
下面是正確的姿勢,我們只是比較他們的值而已

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

那么什么時(shí)候用裝箱的基本類型呢良价?
1.作為集合中的元素寝殴,鍵和值
2.在進(jìn)行反射的方法調(diào)用的時(shí)候,必須使用裝箱基本類型
總結(jié)
1.基本類型要優(yōu)于裝箱基本類型明垢,因?yàn)檠b箱基本類型是對(duì)象蚣常,會(huì)有不必要的開銷
2.提防空指針

7.6如果其他類型更適合,避免使用字符串

字符串袖外,感覺我們開發(fā)中是非常便捷開發(fā)的一種方式史隆,也是下意識(shí)的一種開發(fā)方式魂务。下面是幾條使用字符串的規(guī)范

  • 字符創(chuàng)不適合帶起其他的值類型
  • 字符串不適合代替枚舉類型(枚舉更適合)
  • 字符串不適合代替聚集類型
  • 字符串也不適合代替能力表曼验。(ThreadLocal)

7.7當(dāng)心字符串連接的性能

不要使用字符串連接操作合并多個(gè)字符串,除非性能無關(guān)緊要粘姜。相反鬓照,應(yīng)該使用StringBuilder的append方法。另一種方法是孤紧,使用字符數(shù)組豺裆,或者每次只處理一個(gè)字符串,而不是將它們組合起來。

7.8通過接口愛引用對(duì)象

  • 如果有合適的接口類型存在臭猜,那么對(duì)于參數(shù)躺酒、返回值、變量和域來說蔑歌,都應(yīng)該使用接口類進(jìn)行聲明
  • 如果程序中使用了只有某個(gè)類特有的方法羹应,只能使用類實(shí)現(xiàn)
  • 如果沒有合適的接口存在,完全可以用類而不是接口來引用對(duì)象

7.9接口優(yōu)于反射機(jī)制

反射可以讓我們通過程序來訪問關(guān)于已裝載的類的信息次屠。通過程序訪問類的成員名稱园匹、域類型、方法簽名等信息劫灶。但是反射也要付出代價(jià)

  • 喪失了編譯時(shí)類型檢查的好處裸违;包括異常檢查。如果程序用反射方式來調(diào)用不存在的或者不可訪問的方法本昏,運(yùn)行時(shí)它將會(huì)失敗
  • 執(zhí)行反射訪問所需要的代碼非常笨拙和冗長供汛。
  • 性能損失。反射方法調(diào)用比普通方法調(diào)用慢很多涌穆。
    因此紊馏,普通應(yīng)用程序在運(yùn)行的時(shí)候不應(yīng)該以反射方式訪問對(duì)象
    什么時(shí)候用反射?
  • 提供成熟的服務(wù)者框架蒲犬!絕大多數(shù)情況下使用反射都是這種情況

7.10謹(jǐn)慎地使用本地方法

我們看源碼的時(shí)候知道有一些被native修飾的方法朱监,他們稱為本地方法,一般是用C or C++編寫的原叮。沒啥事不要調(diào)用就對(duì)了赫编!極少數(shù)情況下需要使用本地方法來提供性能。(沒有熟讀底層代碼之前還是慎用7芰ァ)

7.11謹(jǐn)慎地進(jìn)行優(yōu)化

  • 不要隨便優(yōu)化擂送,不要計(jì)較效率上的一些小小的損失
  • 要努力編寫好的程序而不是快的程序。好的程序體現(xiàn)信息隱藏的原則:只要有可能唯欣,他們就會(huì)把設(shè)計(jì)決策集中在單個(gè)模塊中嘹吨,因此可以改變單個(gè)決策,而不會(huì)影響到系統(tǒng)的其他部分境氢。
  • 努力避免那些限制性能舊的設(shè)計(jì)決策
  • 每次試圖進(jìn)行優(yōu)化之前和之后蟀拷,要對(duì)性能進(jìn)行測量
    總結(jié):
    不要費(fèi)力地編寫快速的程序,應(yīng)該努力編寫好的程序萍聊。在設(shè)計(jì)系統(tǒng)的時(shí)候问芬,特別是設(shè)計(jì)API的時(shí)候,一定要考慮性能因素寿桨。如果要優(yōu)化的話:首先要想到的時(shí)候檢查所選擇的算法此衅!再多的底層優(yōu)化也無法彌補(bǔ)算法的選擇不當(dāng)

7.12遵守命名規(guī)范

  • 包名稱。通過域名的反寫比如com.xxx。鼓勵(lì)使用有意義的縮寫只取首字母縮寫也是可以接受的挡鞍。
  • 常量要全部大寫而且要以_分割

8.異常

8.1只針對(duì)異常情況才使用異常

異常時(shí)為了在異常情況下使用而設(shè)計(jì)的骑歹,不要講它們用于普通的控制流。如果用于普通的控制流墨微,異常的性能開銷非常大

8.2對(duì)可恢復(fù)的情況使用受檢異常陵刹,對(duì)編程錯(cuò)誤使用運(yùn)行時(shí)異常

8.3不要過分地使用受檢異常

8.4優(yōu)先使用標(biāo)準(zhǔn)的異常

8.5拋出精確的異常

我們有時(shí)候?yàn)榱朔奖銥閏atch Exception異常。但是不提倡這么做欢嘿,最好是catch相對(duì)應(yīng)的異常

8.6異常中需要攜帶一些有用的信息

我們拋出異常的時(shí)候衰琐,可以帶一些參數(shù),或者有效的信息方便我們排查炼蹦,不要單純打印堆棧就算了

8.7努力使失敗保持原子性

我們知道原子性是要么都成功羡宙,要么都失敗。我們?cè)谔幚懋惓5臅r(shí)候掐隐,如果中間的計(jì)算邏輯出現(xiàn)異常了狗热,要保持之前變量的原子性,不能改變其狀態(tài)虑省。策略有如下策略:

  • 調(diào)整計(jì)算處理過程的順序匿刮,使得任何可能會(huì)失敗的計(jì)算部分都在對(duì)象狀態(tài)被改變之前
  • 數(shù)據(jù)庫回滾邏輯
  • 在操作對(duì)象的時(shí)候,臨時(shí)拷貝一份探颈。當(dāng)所有操作都完成后熟丸,采用臨時(shí)的拷貝對(duì)象替換原來的對(duì)象。例如Collections.sort在執(zhí)行排序之前就會(huì)先把輸入列表轉(zhuǎn)到一個(gè)數(shù)組里面伪节。如果排序失敗光羞,也能保證輸入列表的順序。

9.并發(fā)

9.1同步訪問共享的可變數(shù)據(jù)

同步不僅可以阻止一個(gè)線程看到對(duì)象處于不一致的狀態(tài)之中怀大,它還可以保證進(jìn)入同步方法或者同步代碼塊的每個(gè)線程纱兑,都看到由同一個(gè)鎖保護(hù)的之前所有修改效果(簡而言之,就是所有線程看起來數(shù)據(jù)是一致的化借。)

  • 為了在線程之間進(jìn)行可靠的通信潜慎,也為了互斥訪問,同步是必要的
  • 當(dāng)多個(gè)線程共享可變數(shù)據(jù) 的時(shí)候蓖康,每個(gè)讀或者寫都需要同步

9.2不過過度使用同步

同步的性能損耗非常大铐炫,雖然說JVM已經(jīng)做了優(yōu)化。但是在用同步之前要思考钓瞭,有沒有更好的替代方式~

9.3 executor和task優(yōu)于線程

我們?cè)谑褂枚嗑€程的時(shí)候最優(yōu)雅的方式是使用線程池驳遵,而且最好事自己通過ThreadPoolExecutor類來控制(參考阿里巴巴開發(fā)規(guī)約)

9.4并發(fā)工具優(yōu)于wait和notify

記得我們剛學(xué)習(xí)的時(shí)候淫奔,或者說看馬士兵視頻的時(shí)候都會(huì)講wait山涡,notify。那時(shí)候也不知道在生產(chǎn)上有什么用。但是現(xiàn)在幾乎沒有理由用這兩個(gè)玩意了鸭丛【呵睿可以使用JUC中提供的并發(fā)容器,同步器滿足我們的需求(就是信號(hào)量鳞溉,柵欄瘾带,倒計(jì)數(shù)鎖,比較常用的是CountDownLatch,Semaphore)

9.5寫文檔

在創(chuàng)建一個(gè)類或者涉及到并發(fā)的時(shí)候熟菲,要用文檔進(jìn)行說明~

9.6慎用延遲初始化

  • 在大多數(shù)情況下看政,正常的初始化要優(yōu)于延遲初始化
  • 如果出于性能的考慮而需要對(duì)實(shí)例域使用延遲初始化,就是用雙重檢查模式(保證線程安全抄罕,單例設(shè)計(jì)模式在多線程環(huán)境下可以這樣用)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末允蚣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子呆贿,更是在濱河造成了極大的恐慌嚷兔,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件做入,死亡現(xiàn)場離奇詭異冒晰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)竟块,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門壶运,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浪秘,你說我怎么就攤上這事前弯。” “怎么了秫逝?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵恕出,是天一觀的道長。 經(jīng)常有香客問我违帆,道長浙巫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任刷后,我火速辦了婚禮的畴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尝胆。我一直安慰自己丧裁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布含衔。 她就那樣靜靜地躺著煎娇,像睡著了一般二庵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缓呛,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天催享,我揣著相機(jī)與錄音,去河邊找鬼哟绊。 笑死因妙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的票髓。 我是一名探鬼主播攀涵,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼洽沟!你這毒婦竟也來了汁果?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤玲躯,失蹤者是張志新(化名)和其女友劉穎据德,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跷车,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棘利,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朽缴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片善玫。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖密强,靈堂內(nèi)的尸體忽然破棺而出茅郎,到底是詐尸還是另有隱情,我是刑警寧澤或渤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布系冗,位于F島的核電站,受9級(jí)特大地震影響薪鹦,放射性物質(zhì)發(fā)生泄漏掌敬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一池磁、第九天 我趴在偏房一處隱蔽的房頂上張望奔害。 院中可真熱鬧,春花似錦地熄、人聲如沸华临。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雅潭。三九已至揭厚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寻馏,已是汗流浹背棋弥。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工核偿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诚欠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓漾岳,卻偏偏與公主長得像轰绵,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尼荆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容