何時以及如何創(chuàng)建對象,何時以及如何避免創(chuàng)建對象
如何確保適時銷毀,如何管理對象銷毀前必要的清理
第1條 考慮用靜態(tài)工廠方法代替構(gòu)造器
靜態(tài)工廠方法是一個返回類的實(shí)例的靜態(tài)方法,對它其實(shí)只是一個普通的靜態(tài)方法而已,需要注意的是它與設(shè)計(jì)模式中的工廠方法不同,不要弄混淆了
Java源碼中的例子:
public static Boolean valueOf(boolean b){
return b?Boolean.TRUE:Boolean.FALSE;
}
優(yōu)勢
列舉靜態(tài)工廠方法與構(gòu)造器相比,有哪些優(yōu)勢
靜態(tài)工廠方法有名稱
構(gòu)造方法沒有名稱,給靜態(tài)工廠取一個適當(dāng)?shù)姆椒Q會更易于閱讀,也更易于客戶端使用.
特別是在面對有多個參數(shù)或者多個構(gòu)造方法的類的時候,會讓客戶端不知道如何選擇構(gòu)造方法(自行腦補(bǔ)一個大寫的懵B).
不必在每次調(diào)用它們的時候都創(chuàng)建一個新對象
我們知道構(gòu)造方法每次都會創(chuàng)建一個新的實(shí)例,而當(dāng)我們需要避免重復(fù)創(chuàng)建不必要的對象(或者不可變的類)時通過構(gòu)造方法去做是做不到的,而靜態(tài)方法可以為重復(fù)的調(diào)用返回相同的對象(比如返回的對象需要是一個單例)
它們可以返回原返回類型的任何子類型的對象
構(gòu)造方法只能返回類本身,而靜態(tài)方法可以返回它的子類,利用多態(tài)的特性使得靜態(tài)方法更加靈活
比如Executors
類的各種newxxx
方法返回ExecutorService
的子類
在創(chuàng)建參數(shù)化類型實(shí)例的時候,它們使代碼變得更加簡潔
調(diào)用參數(shù)化構(gòu)造器時,即使類型參數(shù)很明顯也必須指明.
書中列舉了實(shí)例化HashMap的例子:
Map<String,List<String>>m = new HashMap<Stirng,List<String>>();
如果用靜態(tài)工廠可以這么寫:
public static <K,V>HashMap<K,V>newInstance(){
reutrn new HashMap<K,V>();
}
想想以前也確實(shí)煩這些類型指定,一寫一長串,但是現(xiàn)在的JVMS(Java1.7)已經(jīng)實(shí)現(xiàn)了類型推導(dǎo),所以其實(shí)也沒那么復(fù)雜了.
劣勢
靜態(tài)工廠方法的劣勢
類如果不含公有的或者受保護(hù)的構(gòu)造器,就不能被子類化
它們與其他的靜態(tài)方法實(shí)際上沒有任何區(qū)別
確實(shí),靜態(tài)方法真的只是一個靜態(tài)的方法而已,如果文檔上不提到創(chuàng)建該類的實(shí)例可以使用靜態(tài)方法的時候,很容易被忽略
所以能我們需要遵守一定的命名習(xí)慣:
靜態(tài)方法推薦的命名名稱
- valueOf 返回的實(shí)例與參數(shù)具有相同的值,一般用于類型轉(zhuǎn)換(如String Boolean Double等等)
- of valueOf的簡潔代替
- getInstance 返回的實(shí)例工具參數(shù)而定,對于單例,則一般沒有參數(shù),并且每次返回的都是同一個實(shí)例
- newInstance 每次返回一個新的實(shí)例
- getType
- newType
不能很好的擴(kuò)展到大量的可選參數(shù)
當(dāng)參數(shù)非常多的時候靜態(tài)方法的可讀性,維護(hù)性等就非常差了,這個后面會說
實(shí)際運(yùn)用
現(xiàn)在在我們新建一個Fragment的時候,AS會自動生成一個newInstance(String ,String )
的一個方法,這個就是靜態(tài)工廠方法了,可見官方也推薦我們使用靜態(tài)工廠方法.
另外Java源碼中也有非常多的靜態(tài)工廠方法的運(yùn)用,比如上面提及的Executors
,自己去體會~
小結(jié)
靜態(tài)工廠方法與構(gòu)造器各有優(yōu)勢,不過一般來說靜態(tài)工廠通常更加合適,所以以后多考慮使用靜態(tài)工廠吧.
第2條 遇到多個構(gòu)造器參數(shù)時要考慮用構(gòu)建器(Builder)
當(dāng)實(shí)例化一個類時有很多可選參數(shù)的時候,我們或許會寫很多不同參數(shù)的構(gòu)造方法,可讀性會非常差,并且客戶端也不知道在什么時候用什么構(gòu)造器,這是非常非常糟糕的,同樣靜態(tài)方工廠也不能避免,這個時候我們需要用到構(gòu)建器,也即設(shè)計(jì)模式里的Builder模式
Builder模式非常常見了,列舉個列子就過了:
AlertDialog dialog = new AlertDialog.Builder(this)
.setCancelable(true)
//...各種可選參數(shù) 想要什么設(shè)置什么
.setMessage("Builder模式")
.create();
Builder模式雖然能解決多參數(shù)遇到的問題,但是它也有缺點(diǎn):
為了創(chuàng)建對象,我們必須先創(chuàng)建一個Builder,這是一個多余的開銷,雖然開銷并不明顯,但確實(shí)存在.
小結(jié)
當(dāng)有多個參數(shù)的時候,Builder模式是非常不錯的選擇.
第3條 用私有構(gòu)造器或者枚舉類型強(qiáng)化Singleton屬性
所謂Singleton即單例,指僅僅被實(shí)例化一次的類,這一條與單例息息相關(guān).
私有構(gòu)造器強(qiáng)化
寫過單例的一定知道,構(gòu)造器一定要私有,否則別人隨便new,怎么保證單例呢?
書中還講到了單例如何防反射,防反序列化,這里就不提了
枚舉強(qiáng)化
java 1.5 版本之后,單例多了一個實(shí)現(xiàn)方法(包含單個元素的枚舉類型):
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){...}
}
優(yōu)勢:
- 無償提供序列化機(jī)制
- 絕對防止多次實(shí)例化
- 防反射
- 簡潔
書中說:單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法,可惜的是我沒在實(shí)際中用過,也沒見過這種實(shí)現(xiàn)方式.
小結(jié)
單例有很多實(shí)現(xiàn)方式,也是不容易的~
第4條 通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
首先理解什么是不可實(shí)例化?
不可實(shí)例化是指只包含靜態(tài)方法和靜態(tài)域的類
比如BitmapUtil等各種Utils,它們包含各種靜態(tài)方法,但是實(shí)際上我們不會也不需要去實(shí)例化它!
靜態(tài)域(static-field) 比如:
public static Object obj
既然工具類不希望被實(shí)例化,那么如何做呢?
提供一個私有構(gòu)造器,并在里面拋出異常即可
eg:
public class Util{
private Util{
// Suppress default constructor for noninstantiability
throw new AssertionError();
}
}
相信都會感覺這多此一舉,誰tm沒事去實(shí)例化工具類啊,以前我也這么覺得,直到我的膝蓋中了...
偶不,直到我在優(yōu)秀的庫中看到這寫法,比如RxJava中的Subscriptions
類:
public final class Subscriptions {
private Subscriptions() {
throw new IllegalStateException("No instances!");
}
//...
}
又如Jake
大神的RxBinding
中各種RxXXXX
也是如此:
private RxAppBarLayout() {
throw new AssertionError("No instances.");
}
優(yōu)秀的項(xiàng)目喘漏,細(xì)節(jié)處理都非常優(yōu)秀
小結(jié)
有時候有些東西非常有道理,只是自己太弱小,悟不到而已,虛心學(xué)習(xí),keep growing
PS: EffectiveJava是本好書,RxJava是個優(yōu)秀的庫,Jake是真大神T欧帧!
第5條 避免創(chuàng)建不必要的對象
一般的講,最后能重用對象而不是在每次需要的時候創(chuàng)建一個相同功能的新對象.
另外對于一個不可變的對象(immutable 后面會講),它始終可以被重用.
作者舉了幾個例子來說明:
創(chuàng)建String實(shí)例
用String s = "stringette";
替代String s = new String("stringette")
因?yàn)楹笳叩膮?shù)其實(shí)就是一個實(shí)例,每一次調(diào)用都會多創(chuàng)建一個沒用的對象.
Boolean
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
Boolean.valueOf()方法重用了FALSE
和TRUE
來避免重復(fù)創(chuàng)建相同功能的對象
自動裝箱autoboxing
自動裝箱允許我們將基本類型和裝箱基本類型(Boxed Primitive Type)混用,按需自動裝箱和拆箱
它們倆之間性能是有明顯的差別的(基本類型更優(yōu))
eg:
public static void main(String[] args){
Long sum = 0L;
for(long i=0;i<Integer.MAX_VALUE;i++){
sum += i;
}
System.out.println(sum);
}
sum 的類型為Long
,這樣會比long
多創(chuàng)建約2的31次方的Long實(shí)例,影響性能,所需時間大約是long的6倍多,非常可怕啊
so,記住,優(yōu)先使用基本類型!
PS 這里要說的不是創(chuàng)建對象非常昂貴,因?yàn)?em>小對象的創(chuàng)建和回收是非常廉價的
對象池(object pool)
維護(hù)對象池來避免創(chuàng)建對象只針對非常重量級的對象,如數(shù)據(jù)庫連接池
Android中對象池有很多,如Message
類,又如Glide中的Bitmap池
對象池的缺點(diǎn)
- 代碼更亂 涉及到回收、重用必然會增加許多代碼
- 增加內(nèi)存占用,損害性能
小結(jié)
當(dāng)需要重用的時候,就不要創(chuàng)建
第6條 消除過期的對象引用
首先需要明確,Java即使有GC,我們依然要自己考慮內(nèi)存管理的事情.
當(dāng)一個數(shù)組擴(kuò)容后又縮減,比如size從0->200->100(一個棧先增長,后收縮),那么元素的index>=100的那些元素(被pop掉的)都算是過期元素,那些引用就是過期引用(永遠(yuǎn)不會再被解除的引用)
過期引用導(dǎo)致了內(nèi)存泄露
雖然對于自己來說pop掉的元素我們不會去用,但是由于過期引用的存在,GC并不會去回收它們,所以需要我們手動清空這些引用.
eg:
public Object pop(){
if(size==0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; //Eliminate obsolete reference
return result;
}
第7條 避免使用終結(jié)方法
終結(jié)方法:finalizer
(老實(shí)說,這個真沒用過)
沒看懂,記錄一些點(diǎn)..
- 終結(jié)方法會導(dǎo)致行為不穩(wěn)定,降低性能,以及可移植性問題
- 不能保證會被及時地執(zhí)行,而且根本不保證它們會被執(zhí)行(這..好過分..)
還是沒看懂,被自己蠢哭了..