Java中的static關(guān)鍵字用法總結(jié)
一、static 關(guān)鍵字的含義
static是Java50個(gè)關(guān)鍵字之一。static關(guān)鍵字可以用來修飾代碼塊表示靜態(tài)代碼塊,修飾成員變量表示全局靜態(tài)成員變量,修飾方法表示靜態(tài)方法。(注意:不能修飾普通類,除了內(nè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關(guān)鍵字修飾的內(nèi)容都是靜態(tài)的。
靜態(tài)是相對于動(dòng)態(tài)的,動(dòng)態(tài)是指Java程序在JVM上運(yùn)行時(shí),JVM會(huì)根據(jù)程序的需要?jiǎng)討B(tài)創(chuàng)建對象并存儲對象(分配內(nèi)存),對象使命結(jié)束后,對象會(huì)被垃圾回收器銷毀,即內(nèi)存回收由JVM統(tǒng)一管理并分配給其他新創(chuàng)建的對象;靜態(tài)是指Java程序還沒有運(yùn)行時(shí),JVM就會(huì)為加載的類分配空間存儲被static關(guān)鍵字修飾的內(nèi)容;如靜態(tài)成員變量,Java類加載到JVM中,JVM會(huì)把類以及類的靜態(tài)成員變量存儲在方法區(qū),我們知道方法區(qū)是線程共享且很少發(fā)生GC的區(qū)域,所以被static關(guān)鍵字修飾的內(nèi)容都是全局共享的,且只會(huì)為其分配一次存儲空間。 所以當(dāng)類的某些內(nèi)容不屬于對象,而由對象共享即屬于類的時(shí)候,就可以考慮是否用static關(guān)鍵字進(jìn)行修飾。
二、static 關(guān)鍵字的用途
1、修飾代碼塊
類中用static關(guān)鍵字修飾的代碼塊稱為靜態(tài)代碼,反之沒有用static關(guān)鍵字修飾的代碼塊稱為實(shí)例代碼塊。
實(shí)例代碼塊會(huì)隨著對象的創(chuàng)建而執(zhí)行,即每個(gè)對象都會(huì)有自己的實(shí)例代碼塊,表現(xiàn)出來就是實(shí)例代碼塊的運(yùn)行結(jié)果會(huì)影響當(dāng)前對象的內(nèi)容,并隨著對象的銷毀而消失(內(nèi)存回收);而靜態(tài)代碼塊是當(dāng)Java類加載到JVM內(nèi)存中而執(zhí)行的代碼塊,由于類的加載在JVM運(yùn)行期間只會(huì)發(fā)生一次,所以靜態(tài)代碼塊也只會(huì)執(zhí)行一次。
因?yàn)殪o態(tài)代碼塊的主要作用是用來進(jìn)行一些復(fù)雜的初始化工作,所以靜態(tài)代碼塊跟隨類存儲在方法區(qū)的表現(xiàn)形式是靜態(tài)代碼塊執(zhí)行的結(jié)果存儲在方法區(qū),即初始化量存儲在方法區(qū)并被線程共享。
2、修飾成員變量
類中用static關(guān)鍵字修飾的成員變量稱為靜態(tài)成員變量,因?yàn)閟tatic不能修飾局部變量(為什么?),因此靜態(tài)成員變量也能稱為靜態(tài)變量。靜態(tài)變量跟代碼塊類似,在類加載到JVM內(nèi)存中,JVM會(huì)把靜態(tài)變量放入方法區(qū)并分配內(nèi)存,也由線程共享。訪問形式是:類名.靜態(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;
}運(yùn)行結(jié)果:
D : 靜態(tài)代碼塊1
2
2
靜態(tài)變量存儲在類的信息中,且可以在線程間共享,那么它當(dāng)然也屬于該類的每個(gè)對象,因此可以通過對象訪問靜態(tài)變量,但編譯器并不支持這么做,且會(huì)給出警告。
注意:
一個(gè)類的靜態(tài)變量和該類的靜態(tài)代碼塊的加載順序。類會(huì)優(yōu)先加載靜態(tài)變量,然后加載靜態(tài)代碼塊,但有多個(gè)靜態(tài)變量和多個(gè)代碼塊時(shí),會(huì)按照編寫的順序進(jìn)行加載。
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;
}可以想一下運(yùn)行的結(jié)果。
- 靜態(tài)變量可以不用顯式的初始化,JVM會(huì)默認(rèn)給其相應(yīng)的默認(rèn)值。如基本數(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內(nèi)存中共享的且可以改變,那么對它的訪問會(huì)引起線程安全問題(線程A改寫的同時(shí),線程B獲取它的值,那么獲取的是修改前的值還是修改后的值呢?),所以使用靜態(tài)變量的同時(shí)要考慮多線程情況。如果能確保靜態(tài)變量不可變,那么可以用final關(guān)鍵字一起使用避免線程安全問題;否則需要采用同步的方式避免線程安全問題,如與volatile關(guān)鍵字一起使用等。
- static關(guān)鍵不能修飾局部變量,包括實(shí)例方法和靜態(tài)方法,不然就會(huì)與static關(guān)鍵字的初衷-共享相違背。
3、修飾方法
static方法一般稱作靜態(tài)方法,由于靜態(tài)方法不依賴于任何對象就可以進(jìn)行訪問,因此對于靜態(tài)方法來說,是沒有this的,因?yàn)樗灰栏接谌魏螌ο?,既然都沒有對象,就談不上this了。并且由于這個(gè)特性,在靜態(tài)方法中不能訪問類的非靜態(tài)成員變量和非靜態(tài)成員方法,因?yàn)榉庆o態(tài)成員方法/變量都是必須依賴具體的對象才能夠被調(diào)用。
但是要注意的是,雖然在靜態(tài)方法中不能訪問非靜態(tài)成員方法和非靜態(tài)成員變量,但是在非靜態(tài)成員方法中是可以訪問靜態(tài)成員方法/變量的。舉個(gè)簡單的例子:

在上面的代碼中,由于print2方法是獨(dú)立于對象存在的,可以直接用過類名調(diào)用。假如說可以在靜態(tài)方法中訪問非靜態(tài)方法/變量的話,那么如果在main方法中有下面一條語句:
MyObject.print2();
此時(shí)對象都沒有,str2根本就不存在,所以就會(huì)產(chǎn)生矛盾了。同樣對于方法也是一樣,由于你無法預(yù)知在print1方法中是否訪問了非靜態(tài)成員變量,所以也禁止在靜態(tài)成員方法中訪問非靜態(tài)成員方法。
而對于非靜態(tài)成員方法,它訪問靜態(tài)成員方法/變量顯然是毫無限制的。
因此,如果說想在不創(chuàng)建對象的情況下調(diào)用某個(gè)方法,就可以將這個(gè)方法設(shè)置為static。我們最常見的static方法就是main方法,至于為什么main方法必須是static的,現(xiàn)在就很清楚了。因?yàn)槌绦蛟趫?zhí)行main方法的時(shí)候沒有創(chuàng)建任何對象,因此只有通過類名來訪問。
三、static 關(guān)鍵字的誤區(qū)
1、static關(guān)鍵字會(huì)改變類中成員的訪問權(quán)限嗎?
有些初學(xué)的朋友會(huì)將java中的static與C/C++中的static關(guān)鍵字的功能混淆了。在這里只需要記住一點(diǎn):與C/C++中的static不同,Java中的static關(guān)鍵字不會(huì)影響到變量或者方法的作用域。在Java中能夠影響到訪問權(quán)限的只有private、public、protected(包括包訪問權(quán)限)這幾個(gè)關(guān)鍵字。看下面的例子就明白了:

提示錯(cuò)誤"Person.age 報(bào)紅",這說明static關(guān)鍵字并不會(huì)改變變量和方法的訪問權(quán)限。
2、能通過this訪問靜態(tài)成員變量嗎?
雖然對于靜態(tài)方法來說沒有this,那么在非靜態(tài)方法中能夠通過this訪問靜態(tài)成員變量嗎?先看下面的一個(gè)例子,這段代碼輸出的結(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);
}
}
輸出結(jié)果:
33
這里面主要考察隊(duì)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)要記住一點(diǎn):靜態(tài)成員變量雖然獨(dú)立于對象,但是不代表不可以通過對象去訪問,所有的靜態(tài)方法和靜態(tài)變量都可以通過對象訪問(只要訪問權(quán)限足夠)。
3、static能作用于局部變量么?
在C/C++中static是可以作用域局部變量的,但是在Java中切記:
static是不允許用來修飾局部變量。不要問為什么,這是Java語法的規(guī)定。
4、static關(guān)鍵字修飾內(nèi)部類
static關(guān)鍵字雖然不能修飾普通類,但可以用static關(guān)鍵字修飾內(nèi)部類使其變成靜態(tài)內(nèi)部類。
static關(guān)鍵字本身的含義就是共享,而Java類加載到JVM內(nèi)存的方法區(qū),也是線程共享的,所以沒必要用static關(guān)鍵字修飾普通類。
四、常見的筆試面試題
下面列舉一些面試筆試中經(jīng)常遇到的關(guān)于static關(guān)鍵字的題目,僅供參考,如有補(bǔ)充歡迎下方留言。
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");
}
}輸出結(jié)果:
base static
test static
base constructor
test constructor
至于為什么是這個(gè)結(jié)果,我們先不討論,先來想一下這段代碼具體的執(zhí)行過程,在執(zhí)行開始,先要尋找到main方法,因?yàn)閙ain方法是程序的入口,但是在執(zhí)行main方法之前,必須先加載Test類,而在加載Test類的時(shí)候發(fā)現(xiàn)Test類繼承自Base類,因此會(huì)轉(zhuǎn)去先加載Base類,在加載Base類的時(shí)候,發(fā)現(xiàn)有static塊,便執(zhí)行了static塊。在Base類加載完成之后,便繼續(xù)加載Test類,然后發(fā)現(xiàn)Test類中也有static塊,便執(zhí)行static塊。在加載完所需的類之后,便開始執(zhí)行main方法。在main方法中執(zhí)行new Test()的時(shí)候會(huì)先調(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");
}
}輸出結(jié)果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
類似地,我們還是來想一下這段代碼的具體執(zhí)行過程。首先加載Test類,因此會(huì)執(zhí)行Test類中的static塊。接著執(zhí)行new MyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時(shí)候,發(fā)現(xiàn)MyClass類繼承自Test類,但是由于Test類已經(jīng)被加載了,所以只需要加載MyClass類,那么就會(huì)執(zhí)行MyClass類的中的static塊。在加載完之后,就通過構(gòu)造器來生成對象。而在生成對象的時(shí)候,必須先初始化父類的成員變量,因此會(huì)執(zhí)行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會(huì)先加載Person類并執(zhí)行Person類中的static塊,接著執(zhí)行父類的構(gòu)造器,完成了父類的初始化,然后就來初始化自身了,因此會(huì)接著執(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");
}
}輸出結(jié)果:
test static 1
test static 2
雖然在main方法中沒有任何語句,但是還是會(huì)輸出,原因上面已經(jīng)講述過了。
另外,static塊可以出現(xiàn)類中的任何地方(只要不是方法內(nèi)部,記住,任何方法內(nèi)部都不行),并且執(zhí)行是按照static塊的順序執(zhí)行的。
五、static關(guān)鍵字的缺點(diǎn)
封裝是Java類的三大特性之一,也是面向?qū)ο蟮闹饕匦浴?/p>
因?yàn)椴恍枰ㄟ^對象,而直接通過類就能訪問類的屬性和方法,這有點(diǎn)破壞類的封裝性;
所以除了Utils類,代碼中應(yīng)該盡量少用static關(guān)鍵字修飾變量和方法。
到此這篇關(guān)于Java中的static關(guān)鍵字用法總結(jié)的文章就介紹到這了,更多相關(guān)Java的static關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java將時(shí)間按月份分段的實(shí)現(xiàn)思路與方法
這篇文章主要給大家介紹了關(guān)于Java將時(shí)間按月份分段的實(shí)現(xiàn)思路與方法,通過文中介紹的方法可以將時(shí)間分成我們想要的時(shí)間段,文中給出了詳細(xì)的實(shí)例代碼,需要的朋友可以參考下2021-07-07
Java實(shí)現(xiàn)天天酷跑小游戲完整代碼(附源碼)
這篇文章主要介紹了使用Java實(shí)現(xiàn)天天酷跑(附源碼),本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
JAVA中跳出當(dāng)前多重嵌套循環(huán)的方法詳解
今天在看面試題時(shí),發(fā)現(xiàn)了這個(gè)問題,因?yàn)樵赑HP中跳出多次循環(huán)可以使用break數(shù)字來跳出多層循環(huán),但這在java中并不好使,這篇文章主要給大家介紹了關(guān)于JAVA中跳出當(dāng)前多重嵌套循環(huán)的相關(guān)資料,需要的朋友可以參考下2022-01-01
Hibernate中l(wèi)oad方法與get方法的區(qū)別
Hibernate中有兩個(gè)極為相似的方法get()與load(),他們都可以通過指定的實(shí)體類與ID從數(shù)據(jù)庫中讀取數(shù)據(jù),并返回對應(yīng)的實(shí)例,但Hibernate不會(huì)搞兩個(gè)完全一樣的方法的2016-01-01
基于Jenkins+Maven+Gitea+Nexus搭建CICD環(huán)境的方式
這篇文章主要介紹了基于Jenkins+Maven+Gitea+Nexus從0到1搭建CICD環(huán)境,大家都知道Nexus是一套“開箱即用”的系統(tǒng)不需要數(shù)據(jù)庫,它使用文件系統(tǒng)加Lucene來組織數(shù)據(jù),需要的朋友可以參考下2022-01-01
Spring Boot開箱即用可插拔實(shí)現(xiàn)過程演練與原理解析
本文通過深入探討Spring Boot的背景歷史、業(yè)務(wù)場景、功能點(diǎn)以及底層原理,并通過Java代碼手寫模擬了Spring Boot的啟動(dòng)過程和自動(dòng)配置功能,為開發(fā)者提供了一個(gè)全面的理解,感興趣的朋友跟隨小編一起看看吧2024-11-11
SpringBoot實(shí)現(xiàn)子類的反序列化示例代碼
這篇文章主要給大家介紹了關(guān)于SpringBoot實(shí)現(xiàn)子類的反序列化的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
5分鐘教你使用java搞定網(wǎng)站登錄驗(yàn)證碼
這篇文章主要為大家介紹了使用java搞定網(wǎng)站登錄驗(yàn)證碼的快速實(shí)現(xiàn)方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04

