Java中的static關(guān)鍵字你了解多少
一.static關(guān)鍵字的用途
在《Java編程思想》P86頁有這樣一段話:
“static方法就是沒有this的方法。在static方法內(nèi)部不能調(diào)用非靜態(tài)方法,反過來是可以的。而且可以在沒有創(chuàng)建任何對象的前提下,僅僅通過類本身來調(diào)用static方法。這實際上正是static方法的主要用途。”
這段話雖然只是說明了static方法的特殊之處,但是可以看出static關(guān)鍵字的基本作用,簡而言之,一句話來描述就是:
方便在沒有創(chuàng)建對象的情況下來進(jìn)行調(diào)用(方法/變量)。
很顯然,被static關(guān)鍵字修飾的方法或者變量不需要依賴于對象來進(jìn)行訪問,只要類被加載了,就可以通過類名去進(jìn)行訪問。
static可以用來修飾類的成員方法、類的成員變量,另外可以編寫static代碼塊來優(yōu)化程序性能。
1)static方法
static方法一般稱作靜態(tài)方法,由于靜態(tài)方法不依賴于任何對象就可以進(jìn)行訪問,因此對于靜態(tài)方法來說,是沒有this的,因為它不依附于任何對象,既然都沒有對象,就談不上this了。并且由于這個特性,在靜態(tài)方法中不能訪問類的非靜態(tài)成員變量和非靜態(tài)成員方法,因為非靜態(tài)成員方法/變量都是必須依賴具體的對象才能夠被調(diào)用。
但是要注意的是,雖然在靜態(tài)方法中不能訪問非靜態(tài)成員方法和非靜態(tài)成員變量,但是在非靜態(tài)成員方法中是可以訪問靜態(tài)成員方法/變量的。舉個簡單的例子:
在上面的代碼中,由于print2方法是獨立于對象存在的,可以直接用過類名調(diào)用。假如說可以在靜態(tài)方法中訪問非靜態(tài)方法/變量的話,那么如果在main方法中有下面一條語句:
MyObject.print2();
此時對象都沒有,str2根本就不存在,所以就會產(chǎn)生矛盾了。同樣對于方法也是一樣,由于你無法預(yù)知在print1方法中是否訪問了非靜態(tài)成員變量,所以也禁止在靜態(tài)成員方法中訪問非靜態(tài)成員方法。
而對于非靜態(tài)成員方法,它訪問靜態(tài)成員方法/變量顯然是毫無限制的。
因此,如果說想在不創(chuàng)建對象的情況下調(diào)用某個方法,就可以將這個方法設(shè)置為static。我們最常見的static方法就是main方法,至于為什么main方法必須是static的,現(xiàn)在就很清楚了。因為程序在執(zhí)行main方法的時候沒有創(chuàng)建任何對象,因此只有通過類名來訪問。
另外記住,關(guān)于構(gòu)造器是否是static方法可參考:http://www.dbjr.com.cn/article/236684.htm
2)static變量
static變量也稱作靜態(tài)變量,靜態(tài)變量和非靜態(tài)變量的區(qū)別是:靜態(tài)變量被所有的對象所共享,在內(nèi)存中只有一個副本,它當(dāng)且僅當(dāng)在類初次加載時會被初始化。而非靜態(tài)變量是對象所擁有的,在創(chuàng)建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
static成員變量的初始化順序按照定義的順序進(jìn)行初始化。
3)static代碼塊
static關(guān)鍵字還有一個比較關(guān)鍵的作用就是 用來形成靜態(tài)代碼塊以優(yōu)化程序性能。static塊可以置于類中的任何地方,類中可以有多個static塊。在類初次被加載的時候,會按照static塊的順序來執(zhí)行每個static塊,并且只會執(zhí)行一次。
為什么說static塊可以用來優(yōu)化程序性能,是因為它的特性:只會在類加載的時候執(zhí)行一次。下面看個例子:
class Person{ private Date birthDate; public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBornBoomer() { Date startDate = Date.valueOf("1946"); Date endDate = Date.valueOf("1964"); return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0; } }
isBornBoomer是用來這個人是否是1946-1964年出生的,而每次isBornBoomer被調(diào)用的時候,都會生成startDate和birthDate兩個對象,造成了空間浪費,如果改成這樣效率會更好:
class Person{ private Date birthDate; private static Date startDate,endDate; static{ startDate = Date.valueOf("1946"); endDate = Date.valueOf("1964"); } public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBornBoomer() { return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0; } }
因此,很多時候會將一些只需要進(jìn)行一次的初始化操作都放在static代碼塊中進(jìn)行。
二.static關(guān)鍵字的誤區(qū)
1.static關(guān)鍵字會改變類中成員的訪問權(quán)限嗎?
有些初學(xué)的朋友會將java中的static與C/C++中的static關(guān)鍵字的功能混淆了。在這里只需要記住一點:與C/C++中的static不同,Java中的static關(guān)鍵字不會影響到變量或者方法的作用域。在Java中能夠影響到訪問權(quán)限的只有private
、public
、protected
(包括包訪問權(quán)限)這幾個關(guān)鍵字??聪旅娴睦泳兔靼琢耍?/p>
提示錯誤"Person.age 不可視",這說明static關(guān)鍵字并不會改變變量和方法的訪問權(quán)限。
2.能通過this訪問靜態(tài)成員變量嗎?
雖然對于靜態(tài)方法來說沒有this,那么在非靜態(tài)方法中能夠通過this訪問靜態(tài)成員變量嗎?先看下面的一個例子,這段代碼輸出的結(jié)果是什么?
public class Main { static int value = 33; public static void main(String[] args) throws Exception{ new Main().printValue(); } private void printValue(){ int value = 3; System.out.println(this.value); } }
33
這里面主要考察隊this和static的理解。this代表什么?this代表當(dāng)前對象,那么通過new Main()來調(diào)用printValue的話,當(dāng)前對象就是通過new Main()生成的對象。而static變量是被對象所享有的,因此在printValue中的this.value的值毫無疑問是33。在printValue方法內(nèi)部的value是局部變量,根本不可能與this關(guān)聯(lián),所以輸出結(jié)果是33。在這里永遠(yuǎn)要記住一點:靜態(tài)成員變量雖然獨立于對象,但是不代表不可以通過對象去訪問,所有的靜態(tài)方法和靜態(tài)變量都可以通過對象訪問(只要訪問權(quán)限足夠)。
3.static能作用于局部變量么?
在C/C++中static是可以作用域局部變量的,但是在Java中切記:static是不允許用來修飾局部變量。不要問為什么,這是Java語法的規(guī)定。
三.常見的筆試面試題
下面列舉一些面試筆試中經(jīng)常遇到的關(guān)于static關(guān)鍵字的題目,僅供參考,如有補充歡迎下方留言。
1.下面這段代碼的輸出結(jié)果是什么?
public class Test extends Base{ static{ System.out.println("test static"); } public Test(){ System.out.println("test constructor"); } public static void main(String[] args) { new Test(); } } class Base{ static{ System.out.println("base static"); } public Base(){ System.out.println("base constructor"); } }
base static
test static
base constructor
test constructor
至于為什么是這個結(jié)果,我們先不討論,先來想一下這段代碼具體的執(zhí)行過程,在執(zhí)行開始,先要尋找到main方法,因為main方法是程序的入口,但是在執(zhí)行main方法之前,必須先加載Test類,而在加載Test類的時候發(fā)現(xiàn)Test類繼承自Base類,因此會轉(zhuǎn)去先加載Base類,在加載Base類的時候,發(fā)現(xiàn)有static塊,便執(zhí)行了static塊。在Base類加載完成之后,便繼續(xù)加載Test類,然后發(fā)現(xiàn)Test類中也有static塊,便執(zhí)行static塊。在加載完所需的類之后,便開始執(zhí)行main方法。在main方法中執(zhí)行new Test()的時候會先調(diào)用父類的構(gòu)造器,然后再調(diào)用自身的構(gòu)造器。因此,便出現(xiàn)了上面的輸出結(jié)果。
2.這段代碼的輸出結(jié)果是什么?
public class Test { Person person = new Person("Test"); static{ System.out.println("test static"); } public Test() { System.out.println("test constructor"); } public static void main(String[] args) { new MyClass(); } } class Person{ static{ System.out.println("person static"); } public Person(String str) { System.out.println("person "+str); } } class MyClass extends Test { Person person = new Person("MyClass"); static{ System.out.println("myclass static"); } public MyClass() { System.out.println("myclass constructor"); } }
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
類似地,我們還是來想一下這段代碼的具體執(zhí)行過程。首先加載Test類,因此會執(zhí)行Test類中的static塊。接著執(zhí)行new MyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時候,發(fā)現(xiàn)MyClass類繼承自Test類,但是由于Test類已經(jīng)被加載了,所以只需要加載MyClass類,那么就會執(zhí)行MyClass類的中的static塊。在加載完之后,就通過構(gòu)造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執(zhí)行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會先加載Person類并執(zhí)行Person類中的static塊,接著執(zhí)行父類的構(gòu)造器,完成了父類的初始化,然后就來初始化自身了,因此會接著執(zhí)行MyClass中的Person person = new Person(),最后執(zhí)行MyClass的構(gòu)造器。
3.這段代碼的輸出結(jié)果是什么?
public class Test { static{ System.out.println("test static 1"); } public static void main(String[] args) { } static{ System.out.println("test static 2"); } }
test static 1
test static 2
雖然在main方法中沒有任何語句,但是還是會輸出,原因上面已經(jīng)講述過了。另外,static塊可以出現(xiàn)類中的任何地方(只要不是方法內(nèi)部,記住,任何方法內(nèi)部都不行),并且執(zhí)行是按照static塊的順序執(zhí)行的。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java處理InterruptedException異常的理論與實踐
在使用Java的過程中,有個情景或許很多人見過,您在編寫一個測試程序,程序需要暫停一段時間,于是調(diào)用 Thread.sleep()。但是編譯器或 IDE 報錯說沒有處理檢查到的 InterruptedException。InterruptedException 是什么呢,為什么必須處理它?下面跟著小編一起來看看。2016-08-08Java classloader和namespace詳細(xì)介紹
這篇文章主要介紹了Java classloader和namespace詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-03-03Mybatis注解開發(fā)單表、多表操作的實現(xiàn)代碼
這篇文章主要介紹了Mybatis高級:Mybatis注解開發(fā)單表操作,Mybatis注解開發(fā)多表操作,構(gòu)建sql語句,綜合案例學(xué)生管理系統(tǒng)使用接口注解方式優(yōu)化,需要的朋友可以參考下2021-02-02jstl標(biāo)簽基礎(chǔ)開發(fā)步驟(詳解)
下面小編就為大家?guī)硪黄猨stl標(biāo)簽基礎(chǔ)開發(fā)步驟(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Mybatis-plus:${ew.sqlselect}用法說明
這篇文章主要介紹了Mybatis-plus:${ew.sqlselect}用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06WIN7系統(tǒng)JavaEE(java)環(huán)境配置教程(一)
這篇文章主要介紹了WIN7系統(tǒng)JavaEE(java+tomcat7+Eclipse)環(huán)境配置教程,本文重點在于java配置,感興趣的小伙伴們可以參考一下2016-06-06總結(jié)一下Java回調(diào)機(jī)制的相關(guān)知識
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Java回調(diào)機(jī)制展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06SpringBoot使用slf4j日志并輸出到文件中的操作方法
這篇文章主要介紹了SpringBoot使用slf4j日志并輸出到文件中,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08