帶你全面了解java的static

????????最近跟同事閑聊時聊到了大家對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;

? ? }

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戚哎,一起剝皮案震驚了整個濱河市裸诽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌型凳,老刑警劉巖丈冬,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異甘畅,居然都是意外死亡埂蕊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門疏唾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蓄氧,“玉大人,你說我怎么就攤上這事槐脏『硗” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵顿天,是天一觀的道長堂氯。 經(jīng)常有香客問我,道長牌废,這世上最難降的妖魔是什么咽白? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮鸟缕,結(jié)果婚禮上晶框,老公的妹妹穿的比我還像新娘。我一直安慰自己懂从,他們只是感情好授段,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莫绣,像睡著了一般畴蒲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上对室,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天模燥,我揣著相機與錄音,去河邊找鬼掩宜。 笑死蔫骂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的牺汤。 我是一名探鬼主播辽旋,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了补胚?” 一聲冷哼從身側(cè)響起码耐,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溶其,沒想到半個月后骚腥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瓶逃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年束铭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厢绝。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡契沫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昔汉,到底是詐尸還是另有隱情懈万,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布靶病,位于F島的核電站钞速,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嫡秕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一苹威、第九天 我趴在偏房一處隱蔽的房頂上張望昆咽。 院中可真熱鬧,春花似錦牙甫、人聲如沸掷酗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泻轰。三九已至,卻和暖如春且轨,著一層夾襖步出監(jiān)牢的瞬間浮声,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工旋奢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泳挥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓至朗,卻偏偏與公主長得像屉符,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

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