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í)間為
改進(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));
}
}
優(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));
}
}
}
輸出的結(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里面
- 謹(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熘А!81睹铡!叉抡!寫注釋有時(shí)候不是為了方便別人尔崔,也是為了方便別人!
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");
}
}
}
因?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)境下可以這樣用)