????????最近跟同事閑聊時聊到了大家對java的static關(guān)鍵字的了解,突然發(fā)現(xiàn)很多入行不久的甚至有多年工作經(jīng)驗的同事逻杖,居然都說不清static。尤其是大家通過各種搜索引擎搜到的關(guān)于靜態(tài)方法與非靜態(tài)方法存在哪些共性和差異的時候思瘟,其結(jié)果真是五花八門荸百。豪不夸張的說,很多相關(guān)文章都是是含糊其詞或者根本就不了解滨攻,意度出一些想法就敢發(fā)表各種博客文章够话,這種不負責任的態(tài)度是我所鄙視的,也是當下技術(shù)人員越來越浮躁的一種表現(xiàn)光绕,這些亂七八糟的文章經(jīng)常會把初學者帶入一個又一個誤區(qū)女嘲,讓初學者更加迷茫。
????????在這里我首先介紹一下static基本概念诞帐,比如:static變量澡为、static方法、static代碼塊景埃,但如果想對java里的static關(guān)鍵字有更深層次的理解,光知道表面這些概念是遠遠不夠的顶别。本文帶你深挖static的原理和用法谷徙。
一、基本概念
????????要想深刻理解static關(guān)鍵字驯绎,就不得不談及對象完慧。通常來說,當創(chuàng)建類時剩失,就是在描述那個類的對象的外觀與行為(引自 Thinking in java)屈尼。java是用new來創(chuàng)建類的對象册着,只有在執(zhí)行new()時,數(shù)據(jù)存儲空間才被分配脾歧,其方法才供外界調(diào)用甲捏。
????????然而,什么是外觀和行為呢鞭执?其實司顿,面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒有任何卻別。面向?qū)ο蟮囊胄址模皇歉淖兞宋覀儗栴}的思考方式大溜,使之更接近自然式的思考。當我們把對象拆開估脆,其實對象的外觀就是指數(shù)據(jù)(域)钦奋,對象的行為(方法)就是運行邏輯。我們在編寫類的時候疙赠,其實即編寫了數(shù)據(jù)的結(jié)構(gòu)付材,也編寫了處理數(shù)據(jù)的邏輯。我們一旦拋開“面向?qū)ο蟆边@種“設(shè)計理念”棺聊,而去深入研究底層實現(xiàn)的時候伞租,你會發(fā)現(xiàn)“對象”只是編碼階段產(chǎn)生的觀念,對于機器來說限佩,對象是不存在的葵诈,是我們?yōu)榱俗駨哪撤N理念而虛構(gòu)出來的產(chǎn)物。
????????面向?qū)ο蟮乃季S方式祟同,確實是java語言的一大優(yōu)勢作喘,但static(靜態(tài)修飾符)在某些文字中被稱為“反對象”的存在,因為當聲明一個事物為static時晕城,就意味著這個域或方法不會與包含它的類的任何實例關(guān)聯(lián)在一起泞坦。即使從未創(chuàng)建某個類的任何對象,也可以通過ClassName.filed或ClassName.method()的方式訪問其static域或調(diào)用其static方法砖顷,這也是使用static的首選方式贰锁,并且這種調(diào)用方法在某些情況下還為編譯器進行優(yōu)化提供了更好的機會。當然了滤蝠,先創(chuàng)建對象再通過對象調(diào)用其靜態(tài)域或方法也是可以的豌熄,但是這種對象調(diào)用的方式java并不推薦,因為兩種調(diào)用方式還存在性能方面的差異物咳,因為把字節(jié)碼(.class)文件編譯為機器碼后锣险,指令的多少對運算的速度影響很大。
二、static的幾種使用方式
1芯肤、static變量
????????按照是否為靜態(tài)的對類成員變量進行分類可分兩種:一種是被static修飾的變量巷折,叫靜態(tài)變量或類變量;另一種是沒有被static修飾的變量崖咨,叫實例變量锻拘。兩者的區(qū)別是:
????????對于靜態(tài)變量,在內(nèi)存中只有一個拷貝(節(jié)省內(nèi)存)掩幢,JVM只為靜態(tài)變量分配一次內(nèi)存逊拍,是在加載類的過程中完成靜態(tài)變量的內(nèi)存分配的,可用類名直接訪問(方便)际邻,當然也可以通過對象來訪問(如上文所說芯丧,再次重申,這樣的用法是不被推薦的世曾。下文中遇到通過對象調(diào)用的方式缨恒,只是讓讀者對它的原理更為了解)。例如轮听,下面的代碼就生成了一個靜態(tài)變量骗露,并對其進行了初始化:?
????????你會發(fā)現(xiàn),即使你創(chuàng)建了兩個TestStatic對象血巍,變量 i 在內(nèi)存中也指向的也是同一份存儲空間萧锉,即這兩個對象共享同一個i,因為他們具有相同的值述寡。
????????對于實例變量柿隙,就不是上面這樣啦。系統(tǒng)每創(chuàng)建一個實例鲫凶,就會為實例變量分配一次內(nèi)存禀崖,實例變量可以在內(nèi)存中有多個拷貝,互不影響螟炫。
????????對于靜態(tài)變量波附,有一點你還需要注意,java的規(guī)范中昼钻,是不允許static修飾局部變量的掸屡。
2、靜態(tài)方法
????????盡管當static作用于某個字段時然评,會改變數(shù)據(jù)的創(chuàng)建的方式(因為一個static字段對每個類來說只有一份存儲空間折晦,而非static字段則是對每個對象有一個存儲空間),但是如果static作用于某個方法(也稱實例方法)沾瓦,差別卻沒有那么大。這里說的“沒那么大”,是針對占用內(nèi)存大小來說的贯莺。千萬不要兀自認為實例方法也跟非static變量一樣风喇,也是每個對象都有一份哦。事實是缕探,無論是靜態(tài)方法還是實例方法魂莫,都只存在一份代碼,也就是只占用一份內(nèi)存空間爹耗。
????????那么耙考,既然java中存在靜態(tài)方法和實例方法之分,必然有它們各自的特點潭兽,有哪些不同呢倦始?帶著這個問題,我們繼續(xù)看下面的內(nèi)容山卦。
????????當類的字節(jié)碼文件被加載到內(nèi)存時鞋邑,類的實例方法是不會“立即”被分配入口地址的,只有首次創(chuàng)建該類的對象時账蓉,類中的實例方法才被分配入口地址枚碗,從而實例方法可以被類創(chuàng)建的任何對象調(diào)用執(zhí)行。特別需要注意的是“首次創(chuàng)建該類的對象”铸本,當JVM中已經(jīng)存在該類的對象肮雨,并且在垃圾回收機制回收該類的所有對象之前,再次創(chuàng)建該類的對象是不會再次分配入口地址的箱玷。也就是說怨规,實例方法的入口地址被該類的所有對象共享,當所有的對象都不存在時汪茧,方法的入口地址才被取消(被垃圾回收器在某一時刻自動銷毀)椅亚。這時,便又是一次新的輪回舱污。
????????而對于類中的靜態(tài)方法呀舔,在該類被加載到內(nèi)存時,就分配了相應的入口地址扩灯,從而類方法不僅可以被類創(chuàng)建的任何對象調(diào)用執(zhí)行媚赖,也可以直接通過類名調(diào)用。靜態(tài)方法占用的內(nèi)存空間是不釋放的珠插,其入口地址直到程序退出才被取消惧磺,因此可以把它看作全局方法,它不跟任何類的對象產(chǎn)生關(guān)系捻撑。
????????上面所說的靜態(tài)方法和實例方法內(nèi)存清理方面的不同磨隘,其實源于靜態(tài)方法和實例方法在內(nèi)存中存儲的區(qū)域不同缤底,靜態(tài)方法存儲在靜態(tài)方法區(qū),實例方法存儲在普通方法區(qū)番捂。
????????有一些人認為常駐內(nèi)存是靜態(tài)方法的缺點个唧,實例方法的內(nèi)存就可以被清理,節(jié)省內(nèi)存设预。也有另外一部分人認為靜態(tài)方法在效率上要比實例方法高徙歼,因為實例方法畢竟是首次調(diào)用時才載入內(nèi)存。我只能說我無法對這兩種觀點進行是與非的判斷鳖枕,這種認知屬于太極或者相對論的范疇魄梯。
????????事實上如果一個方法與他所在類的實例對象無關(guān),那么它就應該是靜態(tài)的宾符,而不應該把它寫成實例方法酿秸。 從面向?qū)ο蟮慕嵌壬蟻碚f,在抉擇使用實例化方法或靜態(tài)方法時吸奴,應該根據(jù)是否該方法和實例化對象具有邏輯上的相關(guān)性允扇,如果是就應該使用實例方法,反之使用靜態(tài)方法则奥。這只是從面向?qū)ο蠼嵌壬蟻碚f的考润。
????????當然你完全可以把所有的實例方法都寫成靜態(tài)的,將實例作為參數(shù)傳入即可读处,也不會出什么問題鲜戒。 我只能是善意的告誡:你若想死猎唁,便死锅尘。
????????如果我們繼續(xù)深入研究java語言發(fā)展歷史的話著摔,恐怕就要脫離技術(shù)談理論了。早期的結(jié)構(gòu)化編程管闷,幾乎所有的方法都是“靜態(tài)方法”粥脚,引入實例化方法概念是面向?qū)ο蟾拍畛霈F(xiàn)以后的事情了,區(qū)分靜態(tài)方法和實例化方法不能單單從性能上去理解包个,創(chuàng)建c++,java,c#這樣面向?qū)ο笳Z言的大師引入實例化方法一定不是要解決什么性能刷允、內(nèi)存的問題,而是為了讓開發(fā)更加模式化碧囊、面向?qū)ο蠡髟睢_@樣說的話,靜態(tài)方法和實例化方式的區(qū)分是為了解決模式的問題糯而。(該段摘自網(wǎng)文)
????????對以上內(nèi)容有個透徹的理解之后天通,在使用靜態(tài)方法的時候還需要注意以下幾個方面:
????????在靜態(tài)方法里只能直接調(diào)用同類中其他的靜態(tài)成員(包括變量和方法),而不能直接訪問類中的非靜態(tài)成員熄驼。這是因為像寒,對于非靜態(tài)的方法和變量烘豹,需要先創(chuàng)建類的實例對象后才可使用,而靜態(tài)方法在使用前不用創(chuàng)建任何對象萝映。(備注:靜態(tài)變量是屬于整個類的變量而不是屬于某個對象的)
????????靜態(tài)方法不能以任何方式引用this和super關(guān)鍵字吴叶,因為靜態(tài)方法在使用前不用創(chuàng)建任何實例對象,當靜態(tài)方法調(diào)用時序臂,this所引用的對象根本沒有產(chǎn)生。
3实束、static代碼塊
????????static代碼塊也叫靜態(tài)代碼塊奥秆,是在類中獨立于類成員的static語句塊,可以有多個咸灿,位置可以隨便放构订,它不在任何的方法體內(nèi),JVM加載類時會執(zhí)行這些靜態(tài)的代碼塊避矢,如果static代碼塊有多個悼瘾,JVM將按照它們在類中出現(xiàn)的先后順序依次執(zhí)行它們,每個代碼塊只會被執(zhí)行一次审胸,所以說static塊可以用來優(yōu)化程序性能亥宿。
????????靜態(tài)程序塊:當一個類需要在被載入時就執(zhí)行一段程序,這樣可以使用靜態(tài)程序塊砂沛。
public class DemoClass {
????????private DemoClass(){}
????????public static DemoClass _instance;
????????static{
????????????????if(null == _instance ){
????????????????????????_instance = new DemoClass();
????????????????}
????????}
????????public static DemoClass getInstance(){
????????????????return _instance;
????????}
}
這樣的程序在類被加載的時候就執(zhí)行了static中的代碼烫扼。
4、特殊用法
????????public static ,這對國民CP不用說你也知道碍庵,相信你平時敲代嗎時用了很多次映企,不過,private static你可能就沒怎么用過啦静浴,甚至看到此處還會想堰氓,作者腦子出問題了吧?還有這種玩法苹享?
????????還真有双絮,如果你經(jīng)常讀開源框架的源碼,你會發(fā)想這種用法其實還挺多的富稻。疑問來了:private是私有的掷邦,只有這個類的內(nèi)部能夠訪問,而static不是說好的不屬于類的實例嗎椭赋?抚岗??矛著盾啊哪怔。
????????那么private static與public static的用法有什么區(qū)別呢宣蔚?其實向抢,理解這兩者的區(qū)別并不難,因為(public胚委、private)和static這兩種修飾符的作用本就不同挟鸠,所以要理解兩個的區(qū)別,其實就是這兩種修飾符效果累加起來之后的區(qū)別亩冬。
????????所以艘希,被private static修飾的屬性僅僅可以被靜態(tài)方法調(diào)用,但是只能被本類中的方法(可以是非靜態(tài)的)調(diào)用硅急,在外部創(chuàng)建這個類的對象或者直接使用這個類訪問都是非法的覆享。被public static修飾的屬性除了可以被靜態(tài)方法和非靜態(tài)調(diào)用之外,還可以直接被類名和外部創(chuàng)建的對象調(diào)用营袜。
????????瞧瞧撒顿,private static是合法的,且有著其獨到的用處:為靜態(tài)方法提供私有靜態(tài)屬性荚板。public static常用的是為該對外暴露即可以被類名直接調(diào)用的靜態(tài)常量凤壁。
????????到了這里,你可能發(fā)現(xiàn)我啰嗦這么一段的真正用意啦跪另。上文不是說好的static變量和靜態(tài)方法是建議通過類名去調(diào)用的嗎拧抖?用private修飾后,不是沒法這樣用啦罚斗?而且徙鱼,被private修飾以后,靜態(tài)域或者靜態(tài)方法也失去了其“全局性”罢胱恕袱吆?究竟是要耍哪樣?
????????我覺得這樣使用的設(shè)計者距淫,應該是從以下兩個方面考慮問題的绞绒。
????????1:內(nèi)存的占用角度考慮問題,這是static的初衷榕暇,不做過多講解蓬衡。
????????2:同時,設(shè)計者可能不希望自己的這份代碼被其它類所使用彤枢,以免今后有改動時引發(fā)不必要的麻煩狰晚。
這個世界,本來就沒有一成不變的事情的缴啡。不停的權(quán)衡和取舍壁晒,才是生活的本質(zhì),編程亦是如此业栅。
5秒咐、靜態(tài)內(nèi)部類
????????除以上的幾種static用法之外谬晕,還有一種也是很常用的就是——靜態(tài)內(nèi)部類。
????????如果不知道什么是內(nèi)部類携取,請先自行補過攒钳。見名知意,靜態(tài)內(nèi)部類就是在定義內(nèi)部類的時候雷滋,在其前面加上一個修飾符static不撑,靜態(tài)內(nèi)部類通常被稱為嵌套類。靜態(tài)內(nèi)部類意味著:
????????[1]要創(chuàng)建嵌套類的對象晤斩,并不需要其外圍類的對象燎孟;
????????[2]不能從嵌套類的對象中訪問非靜態(tài)的外圍類對象(不能夠從靜態(tài)內(nèi)部類的對象中訪問外部類的非靜態(tài)成員);
????????嵌套類與普通的內(nèi)部類還有一個區(qū)別:普通內(nèi)部類的字段與方法尸昧,只能放在類的外部層次上,所以普通的內(nèi)部類不能有static數(shù)據(jù)和static字段旷偿,也不能包含嵌套類烹俗。但是在嵌套類里可以包含所有這些東西。也就是說萍程,在非靜態(tài)內(nèi)部類中不可以聲明靜態(tài)成員(static final 變量除外)幢妄,只有將某個內(nèi)部類修飾為靜態(tài)類,然后才能夠在這 個類中定義靜態(tài)的成員變量與成員方法茫负。
????????另外蕉鸳,在創(chuàng)建靜態(tài)內(nèi)部類時不需要將靜態(tài)內(nèi)部類的實例綁定在外部類的實例上。普通非靜態(tài)內(nèi)部類的對象是依附在外部類對象之中的忍法,要在一個外部類中定義一個靜態(tài)的內(nèi)部類潮尝,不需要利用關(guān)鍵字new來創(chuàng)建內(nèi)部類的實例。靜態(tài)類和方法只屬于類本身饿序,并不屬于該類的對象勉失,更不屬于其他外部類的對象。
????????靜態(tài)內(nèi)部類有一個典型的使用場景——單例模式:
public class Singleton {
? ? private Singleton() {}
? ? private static class SingletonInstance {
? ? ? ? private static final Singleton INSTANCE = new Singleton();
? ? }
? ? public static Singleton getInstance() {
? ? ? ? return SingletonInstance.INSTANCE;
? ? }
}
????????這種寫法原探,在實現(xiàn)單例的同時乱凿,也實現(xiàn)了Lazy-Loading的效果。很多面試官都愛考單例及其懶加載咽弦,其實就是考驗應聘者對static的理解有多深徒蟆。但有些面試官偏執(zhí)的認為懶加載要優(yōu)于直接加載,這個我就不贊同啦型型,是否需要懶加載得根據(jù)具體情況來定段审。這讓我不由的詩性大發(fā):梅須遜雪三分白,雪卻輸梅一枝香输莺。
下面是沒有實現(xiàn)懶加載的單例:
public class Singleton {
? ? private final static Singleton INSTANCE = new Singleton();
? ? private Singleton(){}
? ? public static Singleton getInstance(){
? ? ? ? return INSTANCE;
? ? }
}