親妹都能學(xué)會(huì)的 static 關(guān)鍵字
“哥,一周過(guò)去了,教妹學(xué) Java 你都沒(méi)有更新,偷懶了呀!”三妹關(guān)心地問(wèn)我。
“今天就更新。”我面帶著微笑對(duì)三妹說(shuō),“學(xué)習(xí)可不能落下,今天我們來(lái)學(xué) Java 中 static 關(guān)鍵字吧。”
“static 是 Java 中比較難以理解的一個(gè)關(guān)鍵字,也是各大公司的面試官最喜歡問(wèn)到的一個(gè)知識(shí)點(diǎn)之一。”我喝了一口咖啡繼續(xù)說(shuō)道。
“既然是面試重點(diǎn),那我可得好好學(xué)習(xí)下。”三妹連忙說(shuō)。
“static 關(guān)鍵字的作用可以用一句話(huà)來(lái)描述:‘方便在沒(méi)有創(chuàng)建對(duì)象的情況下進(jìn)行調(diào)用,包括變量和方法’。也就是說(shuō),只要類(lèi)被加載了,就可以通過(guò)類(lèi)名進(jìn)行訪問(wèn)。”我扶了扶沉重眼鏡,繼續(xù)說(shuō)到,“static 可以用來(lái)修飾類(lèi)的成員變量,以及成員方法。我們一個(gè)個(gè)來(lái)看。”
01、靜態(tài)變量
“如果在聲明變量的時(shí)候使用了 static 關(guān)鍵字,那么這個(gè)變量就被稱(chēng)為靜態(tài)變量。靜態(tài)變量只在類(lèi)加載的時(shí)候獲取一次內(nèi)存空間,這使得靜態(tài)變量很節(jié)省內(nèi)存空間。”家里的暖氣有點(diǎn)足,我跑去開(kāi)了一點(diǎn)窗戶(hù)后繼續(xù)說(shuō)道。
“來(lái)考慮這樣一個(gè) Student 類(lèi)。”話(huà)音剛落,我就在鍵盤(pán)上噼里啪啦一陣敲。
public class Student { String name; int age; String school = "鄭州大學(xué)";}
這段代碼敲完后,我對(duì)三妹說(shuō):“假設(shè)鄭州大學(xué)錄取了一萬(wàn)名新生,那么在創(chuàng)建一萬(wàn)個(gè) Student 對(duì)象的時(shí)候,所有的字段(name、age 和 school)都會(huì)獲取到一塊內(nèi)存。學(xué)生的姓名和年紀(jì)不盡相同,但都屬于鄭州大學(xué),如果每創(chuàng)建一個(gè)對(duì)象,school 這個(gè)字段都要占用一塊內(nèi)存的話(huà),就很浪費(fèi),對(duì)吧?三妹。”
“因此,最好將 school 這個(gè)字段設(shè)置為 static,這樣就只會(huì)占用一塊內(nèi)存,而不是一萬(wàn)塊。”
安靜的房子里又響起了一陣噼里啪啦的鍵盤(pán)聲。
public class Student { String name; int age; static String school = "鄭州大學(xué)"; public Student(String name, int age) { this.name = name; this.age = age; } public static void main(String[] args) { Student s1 = new Student("沉默王二", 18); Student s2 = new Student("沉默王三", 16); }}
“瞧,三妹。s1 和 s2 這兩個(gè)引用變量存放在棧區(qū)(stack),沉默王二+18 這個(gè)對(duì)象和沉默王三+16 這個(gè)對(duì)象存放在堆區(qū)(heap),school 這個(gè)靜態(tài)變量存放在靜態(tài)區(qū)。”
“等等,哥,棧、堆、靜態(tài)區(qū)?”三妹的臉上塞滿(mǎn)了疑惑。
“哦哦,別擔(dān)心,三妹,畫(huà)幅圖你就全明白了。”說(shuō)完我就打開(kāi) draw.io 這個(gè)網(wǎng)址,認(rèn)真地畫(huà)起了圖。
“現(xiàn)在,是不是一下子就明白了?”看著這幅漂亮的手繪圖,我心里有點(diǎn)小開(kāi)心。
“哇,哥,驚艷了呀!”三妹也不忘拍馬屁,給我了一個(gè)大大的贊。
“好了,三妹,我們來(lái)看下面這串代碼。”
public class Counter { int count = 0; Counter() { count++; System.out.println(count); } public static void main(String args[]) { Counter c1 = new Counter(); Counter c2 = new Counter(); Counter c3 = new Counter(); }}
“我們創(chuàng)建一個(gè)成員變量 count,并且在構(gòu)造函數(shù)中讓它自增。因?yàn)槌蓡T變量會(huì)在創(chuàng)建對(duì)象的時(shí)候獲取內(nèi)存,因此每一個(gè)對(duì)象都會(huì)有一個(gè) count 的副本, count 的值并不會(huì)隨著對(duì)象的增多而遞增。”
我在侃侃而談,而三妹似乎有些不太明白。
“沒(méi)關(guān)系,三妹,你先盲猜一下,這段代碼輸出的結(jié)果是什么?”
“按照你的邏輯,應(yīng)該輸出三個(gè) 1?是這樣嗎?”三妹眨眨眼,有點(diǎn)不太自信地回答。
“哎呀,不錯(cuò)喲。”
我在 IDEA 中點(diǎn)了一下運(yùn)行按鈕,程序跑了起來(lái)。
111
“每創(chuàng)建一個(gè) Counter 對(duì)象,count 的值就從 0 自增到 1。三妹,想一下,如果 count 是靜態(tài)的呢?”
“我不知道啊。”
“嗯,來(lái)看下面這段代碼。”
public class StaticCounter { static int count = 0; StaticCounter() { count++; System.out.println(count); } public static void main(String args[]) { StaticCounter c1 = new StaticCounter(); StaticCounter c2 = new StaticCounter(); StaticCounter c3 = new StaticCounter(); }}
“來(lái)看一下輸出結(jié)果。”
123
“簡(jiǎn)單解釋一下哈,由于靜態(tài)變量只會(huì)獲取一次內(nèi)存空間,所以任何對(duì)象對(duì)它的修改都會(huì)得到保留,所以每創(chuàng)建一個(gè)對(duì)象,count 的值就會(huì)加 1,所以最終的結(jié)果是 3,明白了吧?三妹。這就是靜態(tài)變量和成員變量之間的差別。”
“另外,需要注意的是,由于靜態(tài)變量屬于一個(gè)類(lèi),所以不要通過(guò)對(duì)象引用來(lái)訪問(wèn),而應(yīng)該直接通過(guò)類(lèi)名來(lái)訪問(wèn),否則編譯器會(huì)發(fā)出警告。”
02、 靜態(tài)方法
“說(shuō)完靜態(tài)變量,我們來(lái)說(shuō)靜態(tài)方法。”說(shuō)完,我準(zhǔn)備點(diǎn)一支華子來(lái)抽,三妹阻止了我,她指一指煙盒上的「吸煙有害身體健康」,我笑了。
“好吧。”我只好喝了一口咖啡繼續(xù)說(shuō),“如果方法上加了 static 關(guān)鍵字,那么它就是一個(gè)靜態(tài)方法。”
“靜態(tài)方法有以下這些特征。”
靜態(tài)方法屬于這個(gè)類(lèi)而不是這個(gè)類(lèi)的對(duì)象;
調(diào)用靜態(tài)方法的時(shí)候不需要?jiǎng)?chuàng)建這個(gè)類(lèi)的對(duì)象;
靜態(tài)方法可以訪問(wèn)靜態(tài)變量。
“來(lái),繼續(xù)上代碼”
public class StaticMethodStudent { String name; int age; static String school = "鄭州大學(xué)"; public StaticMethodStudent(String name, int age) { this.name = name; this.age = age; } static void change() { school = "河南大學(xué)"; } void out() { System.out.println(name + " " + age + " " + school); } public static void main(String[] args) { StaticMethodStudent.change(); StaticMethodStudent s1 = new StaticMethodStudent("沉默王二", 18); StaticMethodStudent s2 = new StaticMethodStudent("沉默王三", 16); s1.out(); s2.out(); }}
“仔細(xì)聽(tīng),三妹。change() 方法就是一個(gè)靜態(tài)方法,所以它可以直接訪問(wèn)靜態(tài)變量 school,把它的值更改為河南大學(xué);并且,可以通過(guò)類(lèi)名直接調(diào)用 change() 方法,就像 StaticMethodStudent.change() 這樣。”
“來(lái)看一下程序的輸出結(jié)果吧。”
沉默王二 18 河南大學(xué)沉默王三 16 河南大學(xué)
“需要注意的是,靜態(tài)方法不能訪問(wèn)非靜態(tài)變量和調(diào)用非靜態(tài)方法。你看,三妹,我稍微改動(dòng)一下代碼,編譯器就會(huì)報(bào)錯(cuò)。”
“先是在靜態(tài)方法中訪問(wèn)非靜態(tài)變量,編譯器不允許。”
“然后在靜態(tài)方法中訪問(wèn)非靜態(tài)方法,編譯器同樣不允許。”
“關(guān)于靜態(tài)方法的使用,這下清楚了吧,三妹?”
看著三妹點(diǎn)點(diǎn)頭,我欣慰地笑了。
“哥,我想到了一個(gè)問(wèn)題,為什么 main 方法是靜態(tài)的啊?”沒(méi)想到,三妹串聯(lián)知識(shí)點(diǎn)的功力還是不錯(cuò)的。
“如果 main 方法不是靜態(tài)的,就意味著 Java 虛擬機(jī)在執(zhí)行的時(shí)候需要先創(chuàng)建一個(gè)對(duì)象才能調(diào)用 main 方法,而 main 方法作為程序的入口,創(chuàng)建一個(gè)額外的對(duì)象顯得非常多余。”我不假思索的回答令三妹感到非常的欽佩。
“java.lang.Math 類(lèi)的幾乎所有方法都是靜態(tài)的,可以直接通過(guò)類(lèi)名來(lái)調(diào)用,不需要?jiǎng)?chuàng)建類(lèi)的對(duì)象。”
03、靜態(tài)代碼塊
“三妹,站起來(lái)活動(dòng)一下,我的脖子都有點(diǎn)僵硬了。”
我們一起走到窗戶(hù)邊,映入眼簾的是從天而降的雪花。三妹和我都高興壞了,迫不及待地打開(kāi)窗口,伸出手去觸摸雪花的溫度,那種稍縱即逝的冰涼,真的舒服極了。
“北國(guó)風(fēng)光,千里冰封,萬(wàn)里雪飄。望長(zhǎng)城內(nèi)外,惟余莽莽;大河上下,頓失滔滔。山舞銀蛇,原馳蠟象,欲與天公試比高。須晴日,看紅裝素裹,分外妖嬈。。。。。。”三妹竟然情不自禁地朗誦起了《沁園春·雪》。
確實(shí)令人欣喜,這是 2020 年洛陽(yáng)的第一場(chǎng)雪,的確令人感到開(kāi)心。
片刻之后。
“除了靜態(tài)變量和靜態(tài)方法,static 關(guān)鍵字還有一個(gè)重要的作用。”我心情愉悅地對(duì)三妹說(shuō),“用一個(gè) static 關(guān)鍵字,外加一個(gè)大括號(hào)括起來(lái)的代碼被稱(chēng)為靜態(tài)代碼塊。”
“就像下面這串代碼。”
public class StaticBlock { static { System.out.println("靜態(tài)代碼塊"); } public static void main(String[] args) { System.out.println("main 方法"); }}
“靜態(tài)代碼塊通常用來(lái)初始化一些靜態(tài)變量,它會(huì)優(yōu)先于 main() 方法執(zhí)行。”
“來(lái)看一下程序的輸出結(jié)果吧。”
靜態(tài)代碼塊main 方法
“二哥,既然靜態(tài)代碼塊先于 main() 方法執(zhí)行,那沒(méi)有 main() 方法的 Java 類(lèi)能執(zhí)行成功嗎?”三妹的腦回路越來(lái)越令我敬佩了。
“Java 1.6 是可以的,但 Java 7 開(kāi)始就無(wú)法執(zhí)行了。”我胸有成竹地回答到。
public class StaticBlockNoMain { static { System.out.println("靜態(tài)代碼塊,沒(méi)有 main"); }}
“在命令行中執(zhí)行 java StaticBlockNoMain 的時(shí)候,會(huì)拋出 NoClassDefFoundError 的錯(cuò)誤。”
“三妹,來(lái)看下面這個(gè)例子。”
public class StaticBlockDemo { public static List
“writes 是一個(gè)靜態(tài)的 ArrayList,所以不太可能在聲明的時(shí)候完成初始化,因此需要在靜態(tài)代碼塊中完成初始化。”
“靜態(tài)代碼塊在初始集合的時(shí)候,真的非常有用。在實(shí)際的項(xiàng)目開(kāi)發(fā)中,通常使用靜態(tài)代碼塊來(lái)加載配置文件到內(nèi)存當(dāng)中。”
04、靜態(tài)內(nèi)部類(lèi)
“三妹啊,除了以上只寫(xiě),static 還有一個(gè)不太常用的功能——靜態(tài)內(nèi)部類(lèi)。”
“Java 允許我們?cè)谝粋€(gè)類(lèi)中聲明一個(gè)內(nèi)部類(lèi),它提供了一種令人信服的方式,允許我們只在一個(gè)地方使用一些變量,使代碼更具有條理性和可讀性。”
“常見(jiàn)的內(nèi)部類(lèi)有四種,成員內(nèi)部類(lèi)、局部?jī)?nèi)部類(lèi)、匿名內(nèi)部類(lèi)和靜態(tài)內(nèi)部類(lèi),限于篇幅原因,前三種不在我們本次的討論范圍之內(nèi),以后有機(jī)會(huì)再細(xì)說(shuō)。”
“來(lái)看下面這個(gè)例子。”三妹有點(diǎn)走神,我敲了敲她的腦袋后繼續(xù)說(shuō)。
public class Singleton { private Singleton() {} private static class SingletonHolder { public static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; }}
“三妹,打起精神,馬上就結(jié)束了。”
“哦哦,這段代碼看起來(lái)很別致啊,哥。”
“是的,三妹,這段代碼在以后創(chuàng)建單例的時(shí)候還會(huì)見(jiàn)到。”
“第一次加載 Singleton 類(lèi)時(shí)并不會(huì)初始化 instance,只有第一次調(diào)用 getInstance()方法時(shí) Java 虛擬機(jī)才開(kāi)始加載 SingletonHolder 并初始化 instance,這樣不僅能確保線(xiàn)程安全,也能保證 Singleton 類(lèi)的唯一性。不過(guò),創(chuàng)建單例更優(yōu)雅的一種方式是使用枚舉,以后再講給你聽(tīng)。”
“需要注意的是。第一,靜態(tài)內(nèi)部類(lèi)不能訪問(wèn)外部類(lèi)的所有成員變量;第二,靜態(tài)內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)的所有靜態(tài)變量,包括私有靜態(tài)變量。第三,外部類(lèi)不能聲明為 static。”
“三妹,你看,在 Singleton 類(lèi)上加 static 后,編譯器就提示錯(cuò)誤了。”
三妹點(diǎn)了點(diǎn)頭,所有所思。
05、ending
“三妹,static 關(guān)鍵字我們就學(xué)到這里吧,你還有什么問(wèn)題嗎?”三妹學(xué)習(xí) Java 的勁頭讓我對(duì)她未來(lái)的編程生涯充滿(mǎn)了信心。
“沒(méi)有了,哥,你講的挺棒的,我已經(jīng)全部都消化了。”三妹的臉上帶著微笑,“對(duì)了,哥,《教妹學(xué) Java》已經(jīng)更新到第 19 講了,你的 PDF 同步更新了嗎?”
本文轉(zhuǎn)載自微信公眾號(hào)「沉默王二」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系沉默王二公眾號(hào)。






