解析Java中的static關(guān)鍵字
static關(guān)鍵字主要有以下5個(gè)使用場(chǎng)景:
1.1、靜態(tài)變量把一個(gè)變量聲明為靜態(tài)變量通常基于以下三個(gè)目的:
作為共享變量使用 減少對(duì)象的創(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; }}
第二種雖然場(chǎng)景不多,但是基本在每個(gè)工程里面都會(huì)使用到,比如聲明Loggger變量:
private static final Logger LOGGER = LogFactory.getLoggger(MyClass.class);
實(shí)際上,如果把static去掉也是可行的,比如:
private final Logger LOGGER = LogFactory.getLoggger(MyClass.class);
這樣一來(lái),對(duì)于每個(gè)MyClass的實(shí)例化對(duì)象都會(huì)擁有一個(gè)LOGGER,如果創(chuàng)建了1000個(gè)MyClass對(duì)象,則會(huì)多出1000個(gè)Logger對(duì)象,造成資源的浪費(fèi),因此通常會(huì)將Logger對(duì)象聲明為static變量,這樣一來(lái),能夠減少對(duì)內(nèi)存資源的占用。
第三種最經(jīng)典的場(chǎng)景莫過(guò)于單例模式了,單例模式由于必須全局只保留一個(gè)副本,所以天然和static的初衷是吻合的,用static來(lái)修飾再合適不過(guò)了。
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)建對(duì)象的情況下調(diào)用。這種使用方式非常地常見(jiàn),比如jdk的Collections類(lèi)中的一些方法、單例模式的getInstance方法、工廠模式的create/build方法、util工具類(lèi)中的方法。
1.3、靜態(tài)代碼塊靜態(tài)代碼塊通常來(lái)說(shuō)是為了對(duì)靜態(tài)變量進(jìn)行一些初始化操作,比如單例模式、定義枚舉類(lèi):
單例模式
public class Singleton { private static Singleton instance; static {instance = new Singleton(); } private Singleton() {} public static Singleton getInstance() {return instance; }}
枚舉類(lèi)
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>(); // 對(duì)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)部類(lèi)
內(nèi)部類(lèi)一般情況下使用不是特別多,如果需要在外部類(lèi)里面定義一個(gè)內(nèi)部類(lèi),通常是基于外部類(lèi)和內(nèi)部類(lèi)有很強(qiáng)關(guān)聯(lián)的前提下才去這么使用。
在說(shuō)靜態(tài)內(nèi)部類(lèi)的使用場(chǎng)景之前,我們先來(lái)看一下靜態(tài)內(nèi)部類(lèi)和非靜態(tài)內(nèi)部類(lèi)的區(qū)別:
非靜態(tài)內(nèi)部類(lèi)對(duì)象持有外部類(lèi)對(duì)象的引用(編譯器會(huì)隱式地將外部類(lèi)對(duì)象的引用作為內(nèi)部類(lèi)的構(gòu)造器參數(shù));而靜態(tài)內(nèi)部類(lèi)對(duì)象不會(huì)持有外部類(lèi)對(duì)象的引用
由于非靜態(tài)內(nèi)部類(lèi)的實(shí)例創(chuàng)建需要有外部類(lèi)對(duì)象的引用,所以非靜態(tài)內(nèi)部類(lèi)對(duì)象的創(chuàng)建必須依托于外部類(lèi)的實(shí)例;而靜態(tài)內(nèi)部類(lèi)的實(shí)例創(chuàng)建只需依托外部類(lèi);
并且由于非靜態(tài)內(nèi)部類(lèi)對(duì)象持有了外部類(lèi)對(duì)象的引用,因此非靜態(tài)內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)的非靜態(tài)成員;而靜態(tài)內(nèi)部類(lèi)只能訪問(wèn)外部類(lèi)的靜態(tài)成員;
兩者的根本性區(qū)別其實(shí)也決定了用static去修飾內(nèi)部類(lèi)的真正意圖:
內(nèi)部類(lèi)需要脫離外部類(lèi)對(duì)象來(lái)創(chuàng)建實(shí)例 避免內(nèi)部類(lèi)使用過(guò)程中出現(xiàn)內(nèi)存溢出第一種是目前靜態(tài)內(nèi)部類(lèi)使用比較多的場(chǎng)景,比如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對(duì)象的時(shí)候Person person = new Person.Builder().name('張三').age(17).build();
第二種情況一般出現(xiàn)在多線程場(chǎng)景下,非靜態(tài)內(nèi)部類(lèi)可能會(huì)引發(fā)內(nèi)存溢出的問(wèn)題,比如下面的例子:
public class Task { public void onCreate() {// 匿名內(nèi)部類(lèi), 會(huì)持有Task實(shí)例的引用new Thread() { public void run() {//...耗時(shí)操作 };}.start(); }}
上面這段代碼中的:
new Thread() { public void run() { //...耗時(shí)操作 };}.start();
聲明并創(chuàng)建了一個(gè)匿名內(nèi)部類(lèi)對(duì)象,該對(duì)象持有外部類(lèi)Task實(shí)例的引用,如果在在run方法中做的是耗時(shí)操作,將會(huì)導(dǎo)致外部類(lèi)Task的實(shí)例遲遲不能被回收,如果Task對(duì)象創(chuàng)建過(guò)多,會(huì)引發(fā)內(nèi)存溢出。
優(yōu)化方式:
public class Task { public void onCreate() {SubTask subTask = new SubTask();subTask.start(); } static class SubTask extends Thread {@Overridepublic void run() { //...耗時(shí)操作 } }}1.5、靜態(tài)導(dǎo)入
靜態(tài)導(dǎo)入其實(shí)就是import static,用來(lái)導(dǎo)入某個(gè)類(lèi)或者某個(gè)包中的靜態(tài)方法或者靜態(tài)變量。如下面這段代碼所示:
import static java.lang.Math.PI; public class MathUtils { public static double calCircleArea(double r) {// 可以直接用 Math類(lèi)中的靜態(tài)變量PIreturn PI * r * r; }}
這樣在書(shū)寫(xiě)代碼的時(shí)候確實(shí)能省一點(diǎn)代碼,但是會(huì)影響代碼可讀性,所以一般情況下不建議這么使用。
二.static變量和普通成員變量區(qū)別static變量和普通成員變量主要有以下4點(diǎn)區(qū)別:
區(qū)別1:所屬不同。static變量屬于類(lèi),不單屬于任何對(duì)象;普通成員變量屬于某個(gè)對(duì)象 區(qū)別2:存儲(chǔ)區(qū)域不同。static變量位于方法區(qū);普通成員變量位于堆區(qū)。 區(qū)別3:生命周期不同。static變量生命周期與類(lèi)的生命周期相同;普通成員變量和其所屬的對(duì)象的生命周期相同。 區(qū)別4:在對(duì)象序列化時(shí)(Serializable),static變量會(huì)被排除在外(因?yàn)閟tatic變量是屬于類(lèi)的,不屬于對(duì)象)三.類(lèi)的構(gòu)造器到底是不是static方法?關(guān)于類(lèi)的構(gòu)造器是否是static方法有很多爭(zhēng)議,在《java編程思想》一書(shū)中提到“類(lèi)的構(gòu)造器雖然沒(méi)有用static修飾,但是實(shí)際上是static方法”,個(gè)人認(rèn)為這種說(shuō)法有點(diǎn)欠妥,原因如下:
1)在類(lèi)的構(gòu)造器中,實(shí)際上有一個(gè)隱藏的參數(shù)this引用,this是跟對(duì)象綁定的,也就是說(shuō)在調(diào)用構(gòu)造器之前,這個(gè)對(duì)象已經(jīng)創(chuàng)建完畢了才能出現(xiàn)this引用。而構(gòu)造器的作用是干什么的呢?它負(fù)責(zé)在創(chuàng)建一個(gè)實(shí)例對(duì)象的時(shí)候?qū)?shí)例進(jìn)行初始化操作,即jvm在堆上為實(shí)例對(duì)象分配了相應(yīng)的存儲(chǔ)空間后,需要調(diào)用構(gòu)造器對(duì)實(shí)例對(duì)象的成員變量進(jìn)行初始化賦值操作。
2)我們?cè)賮?lái)看static方法,由于static不依賴(lài)于任何對(duì)象就可以進(jìn)行訪問(wèn),也就是說(shuō)和this是沒(méi)有任何關(guān)聯(lián)的。從這一層面去講,類(lèi)的構(gòu)造器不是static方法
3)從JVM指令層面去看,類(lèi)的構(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)用類(lèi)的構(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定義很清楚,就是用來(lái)調(diào)用執(zhí)行static方法,而invokespecial用來(lái)調(diào)用實(shí)例方法,用來(lái)特殊調(diào)用父類(lèi)方法、private方法和類(lèi)的構(gòu)造器。
以上就是解析Java中的static關(guān)鍵字的詳細(xì)內(nèi)容,更多關(guān)于Java static 關(guān)鍵字的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. PHP正則表達(dá)式函數(shù)preg_replace用法實(shí)例分析2. 一個(gè) 2 年 Android 開(kāi)發(fā)者的 18 條忠告3. vue使用moment如何將時(shí)間戳轉(zhuǎn)為標(biāo)準(zhǔn)日期時(shí)間格式4. js select支持手動(dòng)輸入功能實(shí)現(xiàn)代碼5. Android 實(shí)現(xiàn)徹底退出自己APP 并殺掉所有相關(guān)的進(jìn)程6. Android studio 解決logcat無(wú)過(guò)濾工具欄的操作7. 什么是Python變量作用域8. vue-drag-chart 拖動(dòng)/縮放圖表組件的實(shí)例代碼9. Spring的異常重試框架Spring Retry簡(jiǎn)單配置操作10. Vue實(shí)現(xiàn)仿iPhone懸浮球的示例代碼
