Java中的static關鍵字用法總結
一、static 關鍵字的含義
static是Java50個關鍵字之一。static關鍵字可以用來修飾代碼塊表示靜態(tài)代碼塊,修飾成員變量表示全局靜態(tài)成員變量,修飾方法表示靜態(tài)方法。(注意:不能修飾普通類,除了內部類,這是為什么?)
class A { static { System.out.println("A : 靜態(tài)代碼塊"); } static int i ; // 靜態(tài)變量 static void method() { System.out.println("A: 靜態(tài)方法"); } }
簡而言之,被static關鍵字修飾的內容都是靜態(tài)的。
靜態(tài)是相對于動態(tài)的,動態(tài)是指Java程序在JVM上運行時,JVM會根據(jù)程序的需要動態(tài)創(chuàng)建對象并存儲對象(分配內存),對象使命結束后,對象會被垃圾回收器銷毀,即內存回收由JVM統(tǒng)一管理并分配給其他新創(chuàng)建的對象;靜態(tài)是指Java程序還沒有運行時,JVM就會為加載的類分配空間存儲被static關鍵字修飾的內容;如靜態(tài)成員變量,Java類加載到JVM中,JVM會把類以及類的靜態(tài)成員變量存儲在方法區(qū),我們知道方法區(qū)是線程共享且很少發(fā)生GC的區(qū)域,所以被static關鍵字修飾的內容都是全局共享的,且只會為其分配一次存儲空間。 所以當類的某些內容不屬于對象,而由對象共享即屬于類的時候,就可以考慮是否用static關鍵字進行修飾。
二、static 關鍵字的用途
1、修飾代碼塊
類中用static關鍵字修飾的代碼塊稱為靜態(tài)代碼,反之沒有用static關鍵字修飾的代碼塊稱為實例代碼塊。
實例代碼塊會隨著對象的創(chuàng)建而執(zhí)行,即每個對象都會有自己的實例代碼塊,表現(xiàn)出來就是實例代碼塊的運行結果會影響當前對象的內容,并隨著對象的銷毀而消失(內存回收);而靜態(tài)代碼塊是當Java類加載到JVM內存中而執(zhí)行的代碼塊,由于類的加載在JVM運行期間只會發(fā)生一次,所以靜態(tài)代碼塊也只會執(zhí)行一次。
因為靜態(tài)代碼塊的主要作用是用來進行一些復雜的初始化工作,所以靜態(tài)代碼塊跟隨類存儲在方法區(qū)的表現(xiàn)形式是靜態(tài)代碼塊執(zhí)行的結果存儲在方法區(qū),即初始化量存儲在方法區(qū)并被線程共享。
2、修飾成員變量
類中用static關鍵字修飾的成員變量稱為靜態(tài)成員變量,因為static不能修飾局部變量(為什么?),因此靜態(tài)成員變量也能稱為靜態(tài)變量。靜態(tài)變量跟代碼塊類似,在類加載到JVM內存中,JVM會把靜態(tài)變量放入方法區(qū)并分配內存,也由線程共享。訪問形式是:類名.靜態(tài)成員名。
public class StaticTest { public static void main(String[] args) { System.out.println(D.i); System.out.println(new D().i); } } class D { static { i = 2; System.out.println("D : 靜態(tài)代碼塊1"); } static int i; }
運行結果:
D : 靜態(tài)代碼塊1
2
2
靜態(tài)變量存儲在類的信息中,且可以在線程間共享,那么它當然也屬于該類的每個對象,因此可以通過對象訪問靜態(tài)變量,但編譯器并不支持這么做,且會給出警告。
注意:
一個類的靜態(tài)變量和該類的靜態(tài)代碼塊的加載順序。類會優(yōu)先加載靜態(tài)變量,然后加載靜態(tài)代碼塊,但有多個靜態(tài)變量和多個代碼塊時,會按照編寫的順序進行加載。
public class Main { public static void main(String[] args) { System.out.println(new D().i); } } class D { static { i = 2; System.out.println("D : 靜態(tài)代碼塊1"); } static { i = 6; System.out.println("D : 靜態(tài)代碼塊2"); } static int i; }
可以想一下運行的結果。
- 靜態(tài)變量可以不用顯式的初始化,JVM會默認給其相應的默認值。如基本數(shù)據(jù)類型的byte為0,short為0,char為\u0000,int為0,long為0L,float為0.0f,double為0.0d,boolean為false,引用類型統(tǒng)一為null。
- 靜態(tài)變量既然是JVM內存中共享的且可以改變,那么對它的訪問會引起線程安全問題(線程A改寫的同時,線程B獲取它的值,那么獲取的是修改前的值還是修改后的值呢?),所以使用靜態(tài)變量的同時要考慮多線程情況。如果能確保靜態(tài)變量不可變,那么可以用final關鍵字一起使用避免線程安全問題;否則需要采用同步的方式避免線程安全問題,如與volatile關鍵字一起使用等。
- static關鍵不能修飾局部變量,包括實例方法和靜態(tài)方法,不然就會與static關鍵字的初衷-共享相違背。
3、修飾方法
static方法一般稱作靜態(tài)方法,由于靜態(tài)方法不依賴于任何對象就可以進行訪問,因此對于靜態(tài)方法來說,是沒有this的,因為它不依附于任何對象,既然都沒有對象,就談不上this了。并且由于這個特性,在靜態(tài)方法中不能訪問類的非靜態(tài)成員變量和非靜態(tài)成員方法,因為非靜態(tài)成員方法/變量都是必須依賴具體的對象才能夠被調用。
但是要注意的是,雖然在靜態(tài)方法中不能訪問非靜態(tài)成員方法和非靜態(tài)成員變量,但是在非靜態(tài)成員方法中是可以訪問靜態(tài)成員方法/變量的。舉個簡單的例子:
在上面的代碼中,由于print2方法是獨立于對象存在的,可以直接用過類名調用。假如說可以在靜態(tài)方法中訪問非靜態(tài)方法/變量的話,那么如果在main方法中有下面一條語句:
MyObject.print2();
此時對象都沒有,str2根本就不存在,所以就會產生矛盾了。同樣對于方法也是一樣,由于你無法預知在print1方法中是否訪問了非靜態(tài)成員變量,所以也禁止在靜態(tài)成員方法中訪問非靜態(tài)成員方法。
而對于非靜態(tài)成員方法,它訪問靜態(tài)成員方法/變量顯然是毫無限制的。
因此,如果說想在不創(chuàng)建對象的情況下調用某個方法,就可以將這個方法設置為static。我們最常見的static方法就是main方法,至于為什么main方法必須是static的,現(xiàn)在就很清楚了。因為程序在執(zhí)行main方法的時候沒有創(chuàng)建任何對象,因此只有通過類名來訪問。
三、static 關鍵字的誤區(qū)
1、static關鍵字會改變類中成員的訪問權限嗎?
有些初學的朋友會將java中的static與C/C++中的static關鍵字的功能混淆了。在這里只需要記住一點:與C/C++中的static不同,Java中的static關鍵字不會影響到變量或者方法的作用域。在Java中能夠影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。看下面的例子就明白了:
提示錯誤"Person.age 報紅",這說明static關鍵字并不會改變變量和方法的訪問權限。
2、能通過this訪問靜態(tài)成員變量嗎?
雖然對于靜態(tài)方法來說沒有this,那么在非靜態(tài)方法中能夠通過this訪問靜態(tài)成員變量嗎?先看下面的一個例子,這段代碼輸出的結果是什么?
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代表當前對象,那么通過new Main()來調用printValue的話,當前對象就是通過new Main()生成的對象。而static變量是被對象所享有的,因此在printValue中的this.value的值毫無疑問是33。在printValue方法內部的value是局部變量,根本不可能與this關聯(lián),所以輸出結果是33。在這里永遠要記住一點:靜態(tài)成員變量雖然獨立于對象,但是不代表不可以通過對象去訪問,所有的靜態(tài)方法和靜態(tài)變量都可以通過對象訪問(只要訪問權限足夠)。
3、static能作用于局部變量么?
在C/C++中static是可以作用域局部變量的,但是在Java中切記:
static是不允許用來修飾局部變量。不要問為什么,這是Java語法的規(guī)定。
4、static關鍵字修飾內部類
static關鍵字雖然不能修飾普通類,但可以用static關鍵字修飾內部類使其變成靜態(tài)內部類。
static關鍵字本身的含義就是共享,而Java類加載到JVM內存的方法區(qū),也是線程共享的,所以沒必要用static關鍵字修飾普通類。
四、常見的筆試面試題
下面列舉一些面試筆試中經常遇到的關于static關鍵字的題目,僅供參考,如有補充歡迎下方留言。
1、下面這段代碼的輸出結果是什么?
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
至于為什么是這個結果,我們先不討論,先來想一下這段代碼具體的執(zhí)行過程,在執(zhí)行開始,先要尋找到main方法,因為main方法是程序的入口,但是在執(zhí)行main方法之前,必須先加載Test類,而在加載Test類的時候發(fā)現(xiàn)Test類繼承自Base類,因此會轉去先加載Base類,在加載Base類的時候,發(fā)現(xiàn)有static塊,便執(zhí)行了static塊。在Base類加載完成之后,便繼續(xù)加載Test類,然后發(fā)現(xiàn)Test類中也有static塊,便執(zhí)行static塊。在加載完所需的類之后,便開始執(zhí)行main方法。在main方法中執(zhí)行new Test()的時候會先調用父類的構造器,然后再調用自身的構造器。因此,便出現(xiàn)了上面的輸出結果。
2、這段代碼的輸出結果是什么?
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類已經被加載了,所以只需要加載MyClass類,那么就會執(zhí)行MyClass類的中的static塊。在加載完之后,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執(zhí)行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會先加載Person類并執(zhí)行Person類中的static塊,接著執(zhí)行父類的構造器,完成了父類的初始化,然后就來初始化自身了,因此會接著執(zhí)行MyClass中的Person person = new Person(),最后執(zhí)行MyClass的構造器。
3、這段代碼的輸出結果是什么?
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方法中沒有任何語句,但是還是會輸出,原因上面已經講述過了。
另外,static塊可以出現(xiàn)類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),并且執(zhí)行是按照static塊的順序執(zhí)行的。
五、static關鍵字的缺點
封裝是Java類的三大特性之一,也是面向對象的主要特性。
因為不需要通過對象,而直接通過類就能訪問類的屬性和方法,這有點破壞類的封裝性;
所以除了Utils類,代碼中應該盡量少用static關鍵字修飾變量和方法。
到此這篇關于Java中的static關鍵字用法總結的文章就介紹到這了,更多相關Java的static關鍵字內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
spring+hibernate 兩種整合方式配置文件的方法
本篇文章主要介紹了spring+hibernate 兩種整合方式配置文件的方法,主要有兩種方式 1、注解方式 2、xml方式實現(xiàn),有興趣的可以了解一下。2017-04-04Spring中@Autowired與@Resource的區(qū)別詳析
@Autowired與@Resource都可以用來裝配bean,都可以寫在字段上,或寫在setter方法上,下面這篇文章主要給大家介紹了關于Spring中@Autowired與@Resource區(qū)別的相關資料,需要的朋友可以參考下2021-10-10Spring中的注解之@Override和@Autowired
看別人寫的代碼,經常會用到 @Override 和 @Autowired 這兩個注解.這邊總結一下這兩個注解的作用,對正在學習java的小伙伴們有很好地幫助,需要的朋友可以參考下2021-05-05Spring @Cacheable redis異常不影響正常業(yè)務方案
這篇文章主要介紹了Spring @Cacheable redis異常不影響正常業(yè)務方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02