解析Java中的static關(guān)鍵字
一.static關(guān)鍵字使用場景
static關(guān)鍵字主要有以下5個(gè)使用場景:
1.1、靜態(tài)變量
把一個(gè)變量聲明為靜態(tài)變量通?;谝韵氯齻€(gè)目的:
- 作為共享變量使用
- 減少對象的創(chuàng)建
- 保留唯一副本
第一種比較容易理解,由于static變量在內(nèi)存中只會(huì)存在一個(gè)副本,所以其可以作為共享變量使用,比如要定義一個(gè)全局配置、進(jìn)行全局計(jì)數(shù)。如:
public class CarConstants { // 全局配置,一般全局配置會(huì)和final一起配合使用, 作為共享變量 public static final int MAX_CAR_NUM = 10000; } public class CarFactory { // 計(jì)數(shù)器 private static int createCarNum = 0; public static Car createCar() { if (createCarNum > CarConstants.MAX_CAR_NUM) { throw new RuntimeException("超出最大可生產(chǎn)數(shù)量"); } Car c = new Car(); createCarNum++; return c; } public static getCreateCarNum() { return createCarNum; } }
第二種雖然場景不多,但是基本在每個(gè)工程里面都會(huì)使用到,比如聲明Loggger變量:
private static final Logger LOGGER = LogFactory.getLoggger(MyClass.class);
實(shí)際上,如果把static去掉也是可行的,比如:
private final Logger LOGGER = LogFactory.getLoggger(MyClass.class);
這樣一來,對于每個(gè)MyClass的實(shí)例化對象都會(huì)擁有一個(gè)LOGGER,如果創(chuàng)建了1000個(gè)MyClass對象,則會(huì)多出1000個(gè)Logger對象,造成資源的浪費(fèi),因此通常會(huì)將Logger對象聲明為static變量,這樣一來,能夠減少對內(nèi)存資源的占用。
第三種最經(jīng)典的場景莫過于單例模式了,單例模式由于必須全局只保留一個(gè)副本,所以天然和static的初衷是吻合的,用static來修飾再合適不過了。
public class Singleton { private static volatile Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
1.2、靜態(tài)方法
將一個(gè)方法聲明為靜態(tài)方法,通常是為了方便在不創(chuàng)建對象的情況下調(diào)用。這種使用方式非常地常見,比如jdk的Collections類中的一些方法、單例模式的getInstance方法、工廠模式的create/build方法、util工具類中的方法。
1.3、靜態(tài)代碼塊
靜態(tài)代碼塊通常來說是為了對靜態(tài)變量進(jìn)行一些初始化操作,比如單例模式、定義枚舉類:
單例模式
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return instance; } }
枚舉類
public enum WeekDayEnum { MONDAY(1,"周一"), TUESDAY(2, "周二"), WEDNESDAY(3, "周三"), THURSDAY(4, "周四"), FRIDAY(5, "周五"), SATURDAY(6, "周六"), SUNDAY(7, "周日"); private int code; private String desc; WeekDayEnum(int code, String desc) { this.code = code; this.desc = desc; } private static final Map<Integer, WeekDayEnum> WEEK_ENUM_MAP = new HashMap<Integer, WeekDayEnum>(); // 對map進(jìn)行初始化 static { for (WeekDayEnum weekDay : WeekDayEnum.values()) { WEEK_ENUM_MAP.put(weekDay.getCode(), weekDay); } } public static WeekDayEnum findByCode(int code) { return WEEK_ENUM_MAP.get(code); } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } }
1.4、靜態(tài)內(nèi)部類
內(nèi)部類一般情況下使用不是特別多,如果需要在外部類里面定義一個(gè)內(nèi)部類,通常是基于外部類和內(nèi)部類有很強(qiáng)關(guān)聯(lián)的前提下才去這么使用。
在說靜態(tài)內(nèi)部類的使用場景之前,我們先來看一下靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類的區(qū)別:
非靜態(tài)內(nèi)部類對象持有外部類對象的引用(編譯器會(huì)隱式地將外部類對象的引用作為內(nèi)部類的構(gòu)造器參數(shù));而靜態(tài)內(nèi)部類對象不會(huì)持有外部類對象的引用
由于非靜態(tài)內(nèi)部類的實(shí)例創(chuàng)建需要有外部類對象的引用,所以非靜態(tài)內(nèi)部類對象的創(chuàng)建必須依托于外部類的實(shí)例;而靜態(tài)內(nèi)部類的實(shí)例創(chuàng)建只需依托外部類;
并且由于非靜態(tài)內(nèi)部類對象持有了外部類對象的引用,因此非靜態(tài)內(nèi)部類可以訪問外部類的非靜態(tài)成員;而靜態(tài)內(nèi)部類只能訪問外部類的靜態(tài)成員;
兩者的根本性區(qū)別其實(shí)也決定了用static去修飾內(nèi)部類的真正意圖:
- 內(nèi)部類需要脫離外部類對象來創(chuàng)建實(shí)例
- 避免內(nèi)部類使用過程中出現(xiàn)內(nèi)存溢出
第一種是目前靜態(tài)內(nèi)部類使用比較多的場景,比如JDK集合中的Entry、builder設(shè)計(jì)模式。
HashMap Entry:
builder設(shè)計(jì)模式:
public class Person { private String name; private int age; private Person(Builder builder) { this.name = builder.name; this.age = builder.age; } public static class Builder { private String name; private int age; public Builder() { } public Builder name(String name) { this.name = name; return this; } public Builder age(int age) { this.age=age; return this; } public Person build() { return new Person(this); } } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } // 在需要?jiǎng)?chuàng)建Person對象的時(shí)候 Person person = new Person.Builder().name("張三").age(17).build();
第二種情況一般出現(xiàn)在多線程場景下,非靜態(tài)內(nèi)部類可能會(huì)引發(fā)內(nèi)存溢出的問題,比如下面的例子:
public class Task { public void onCreate() { // 匿名內(nèi)部類, 會(huì)持有Task實(shí)例的引用 new Thread() { public void run() { //...耗時(shí)操作 }; }.start(); } }
上面這段代碼中的:
new Thread() { public void run() { //...耗時(shí)操作 }; }.start();
聲明并創(chuàng)建了一個(gè)匿名內(nèi)部類對象,該對象持有外部類Task實(shí)例的引用,如果在在run方法中做的是耗時(shí)操作,將會(huì)導(dǎo)致外部類Task的實(shí)例遲遲不能被回收,如果Task對象創(chuàng)建過多,會(huì)引發(fā)內(nèi)存溢出。
優(yōu)化方式:
public class Task { public void onCreate() { SubTask subTask = new SubTask(); subTask.start(); } static class SubTask extends Thread { @Override public void run() { //...耗時(shí)操作 } } }
1.5、靜態(tài)導(dǎo)入
靜態(tài)導(dǎo)入其實(shí)就是import static,用來導(dǎo)入某個(gè)類或者某個(gè)包中的靜態(tài)方法或者靜態(tài)變量。如下面這段代碼所示:
import static java.lang.Math.PI; public class MathUtils { public static double calCircleArea(double r) { // 可以直接用 Math類中的靜態(tài)變量PI return PI * r * r; } }
這樣在書寫代碼的時(shí)候確實(shí)能省一點(diǎn)代碼,但是會(huì)影響代碼可讀性,所以一般情況下不建議這么使用。
二.static變量和普通成員變量區(qū)別
static變量和普通成員變量主要有以下4點(diǎn)區(qū)別:
- 區(qū)別1:所屬不同。static變量屬于類,不單屬于任何對象;普通成員變量屬于某個(gè)對象
- 區(qū)別2:存儲區(qū)域不同。static變量位于方法區(qū);普通成員變量位于堆區(qū)。
- 區(qū)別3:生命周期不同。static變量生命周期與類的生命周期相同;普通成員變量和其所屬的對象的生命周期相同。
- 區(qū)別4:在對象序列化時(shí)(Serializable),static變量會(huì)被排除在外(因?yàn)閟tatic變量是屬于類的,不屬于對象)
三.類的構(gòu)造器到底是不是static方法?
關(guān)于類的構(gòu)造器是否是static方法有很多爭議,在《java編程思想》一書中提到“類的構(gòu)造器雖然沒有用static修飾,但是實(shí)際上是static方法”,個(gè)人認(rèn)為這種說法有點(diǎn)欠妥,原因如下:
1)在類的構(gòu)造器中,實(shí)際上有一個(gè)隱藏的參數(shù)this引用,this是跟對象綁定的,也就是說在調(diào)用構(gòu)造器之前,這個(gè)對象已經(jīng)創(chuàng)建完畢了才能出現(xiàn)this引用。而構(gòu)造器的作用是干什么的呢?它負(fù)責(zé)在創(chuàng)建一個(gè)實(shí)例對象的時(shí)候?qū)?shí)例進(jìn)行初始化操作,即jvm在堆上為實(shí)例對象分配了相應(yīng)的存儲空間后,需要調(diào)用構(gòu)造器對實(shí)例對象的成員變量進(jìn)行初始化賦值操作。
2)我們再來看static方法,由于static不依賴于任何對象就可以進(jìn)行訪問,也就是說和this是沒有任何關(guān)聯(lián)的。從這一層面去講,類的構(gòu)造器不是static方法
3)從JVM指令層面去看,類的構(gòu)造器不是static方法,我們先看一下下面這段代碼:
class Person { private String name; public Person(String name) { this.name = name; } public static void create() { } } public class Main { public static void main(String[] args) { Person.create(); Person p = new Person("Jack"); } }
這段代碼反編譯之后的字節(jié)碼如下:
從上面可以看出,在調(diào)用static方法是調(diào)用的是invokestatic指令,而在調(diào)用類的構(gòu)造器時(shí)實(shí)際上執(zhí)行的是invokespecial指令,而這2個(gè)指令在JVM規(guī)范中的解釋如下:
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokestatic
可以看出,這2個(gè)指令的用途是完全不同的,invokestatic定義很清楚,就是用來調(diào)用執(zhí)行static方法,而invokespecial用來調(diào)用實(shí)例方法,用來特殊調(diào)用父類方法、private方法和類的構(gòu)造器。
以上就是解析Java中的static關(guān)鍵字的詳細(xì)內(nèi)容,更多關(guān)于Java static 關(guān)鍵字的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法
這篇文章主要介紹了JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-10-10Java關(guān)鍵字詳解之final static this super的用法
this用來調(diào)用目前類自身的成員變量,super多用來調(diào)用父類的成員,final多用來定義常量用的,static定義靜態(tài)變量方法用的,靜態(tài)變量方法只能被類本身調(diào)用,下文將詳細(xì)介紹,需要的朋友可以參考下2021-10-10Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問題解決思路及源碼分享
這篇文章主要介紹了Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問題解決思路及源碼分享,具有一定參考價(jià)值,這里分享給大家,供需要的朋友了解。2017-10-10Java中的延遲隊(duì)列DelayQueue詳細(xì)解析
這篇文章主要介紹了Java中的延遲隊(duì)列DelayQueue詳細(xì)解析,JDK自身支持延遲隊(duì)列的數(shù)據(jù)結(jié)構(gòu),其實(shí)類:java.util.concurrent.DelayQueue,<BR>我們通過閱讀源碼的方式理解該延遲隊(duì)列類的實(shí)現(xiàn)過程,需要的朋友可以參考下2023-12-12SpringBoot統(tǒng)一響應(yīng)格式及統(tǒng)一異常處理
在我們開發(fā)SpringBoot后端服務(wù)時(shí),一般需要給前端統(tǒng)一響應(yīng)格式,本文主要介紹了SpringBoot統(tǒng)一響應(yīng)格式及統(tǒng)一異常處理2023-05-05idea新建springboot項(xiàng)目pom文件報(bào)錯(cuò)問題及解決
這篇文章主要介紹了idea新建springboot項(xiàng)目pom文件報(bào)錯(cuò)問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04