提到static關鍵字,相信你們都不陌生,這是相對比較難以理解的一個關鍵字,相信各位也都能深深感覺的到!本篇文章將好好總結一下static這個關鍵字。javascript
在開始講static以前,我想讓各位看一段有意思的代碼:html
public class Test {
static{
System.out.println("test static 1");
}
static{
System.out.println("test static 2");
}
public static void main(String[] args) {
}
}
複製代碼
看完程序,小白童鞋發話了:啥玩意?main方法中啥都沒有,能運行啥?博主你個星星星...java
運行結果:
test static 1
test static 2
複製代碼
小白童鞋:那啥...那啥...博主我說啥了,我啥都沒說...程序員
static的主要意義是在於建立獨立於具體對象的域變量或者方法。以至於即便沒有建立對象,也能使用屬性和調用方法!編程
static關鍵字還有一個比較關鍵的做用就是 用來造成靜態代碼塊以優化程序性能。static塊能夠置於類中的任何地方,類中能夠有多個static塊。在類初次被加載的時候,會按照static塊的順序來執行每一個static塊,而且只會執行一次。安全
爲何說static塊能夠用來優化程序性能,是由於它的特性:只會在類加載的時候執行一次。所以,不少時候會將一些只須要進行一次的初始化操做都放在static代碼塊中進行。dom
一、被static修飾的變量或者方法是獨立於該類的任何對象,也就是說,這些變量和方法不屬於任何一個實例對象,而是被類的實例對象所共享。ide
怎麼理解 「被類的實例對象所共享」 這句話呢?就是說,一個類的靜態成員,它是屬於大夥的【大夥指的是這個類的多個對象實例,咱們都知道一個類能夠建立多個實例!】,全部的類對象共享的,不像成員變量是自個的【自個指的是這個類的單個實例對象】...我以爲我已經講的很通俗了,你明白了咩?性能
二、在該類被第一次加載的時候,就會去加載被static修飾的部分,並且只在類第一次使用時加載並進行初始化,注意這是第一次用就要初始化,後面根據須要是能夠再次賦值的。測試
三、static變量值在類加載的時候分配空間,之後建立類對象的時候不會從新分配。賦值的話,是能夠任意賦值的!
四、被static修飾的變量或者方法是優先於對象存在的,也就是說當一個類加載完畢以後,即使沒有建立對象,也能夠去訪問。
由於static是被類的實例對象所共享,所以若是某個成員變量是被全部對象所共享的,那麼這個成員變量就應該定義爲靜態變量。
所以比較常見的static應用場景有:
一、修飾成員變量 二、修飾成員方法 三、靜態代碼塊 四、修飾類【只能修飾內部類也就是靜態內部類】 五、靜態導包
以上的應用場景將會在下文陸續講到...
靜態變量: static修飾的成員變量叫作靜態變量【也叫作類變量】,靜態變量是屬於這個類,而不是屬因而對象。
實例變量: 沒有被static修飾的成員變量叫作實例變量,實例變量是屬於這個類的實例對象。
還有一點須要注意的是:static是不容許用來修飾局部變量,不要問我問什麼,由於java規定的!
靜態變量: 靜態變量因爲不屬於任何實例對象,屬於類的,因此在內存中只會有一份,在類的加載過程當中,JVM只爲靜態變量分配一次內存空間。
實例變量: 每次建立對象,都會爲每一個對象分配成員變量內存空間,實例變量是屬於實例對象的,在內存中,建立幾回對象,就有幾份成員變量。
怎麼理解呢?打個比喻吧...就比方說程序員小王是一個比較溫柔陽光的男孩子,這1024的這一天,老闆閒的沒事,非要拉着程序員小王來玩耍,怎麼個玩法呢?老闆和小王一人拿着一把菜刀,規則很簡單,互相傷害,一人一刀,你一刀,我一刀....遊戲一開始,老闆二話不說,跳起來就是一刀,程序員小王二話也沒說反手就是一菜刀回去,這個時候老闆發飆了,雙眼瞪得忒大,跳起來又是一刀,這個時候程序員小王不敢還手了,就沒動手。沒想到老闆愈來愈生猛,左一刀右一刀全程下來差很少砍個半個時....程序員小王一直沒有還過手,由於小王知道他是老闆...
這個程序員小王只會在老闆第一次揮刀的時候,回老闆一刀,以後就不還手了,這個時候咱們把程序員小王看作是靜態變量,把老闆第一次向小王揮刀看作是類加載,把小王回老闆一刀看出是分配內存空間,而一人一刀這個回合過程當作是類加載的過程,以後老闆的每一刀都當作是建立一次對象。
連貫起來就是static變量值在類第一次加載的時候分配空間,之後建立類對象的時候不會從新分配
以後這個老闆捱了一刀以後躺醫院了一年,一出院回到公司第一件事就是拉程序員宜春出來玩耍,老闆卻不知其然,這個博主程序員宜春性格異常暴躁,老闆遞給程序員宜春一把菜刀,博主宜春一接過菜刀,猝不及防的被老闆跳起來就是一刀,程序員宜春痛的嗷了一聲,暴躁的程序員宜春還沒嗷完,在嗷的同時跳起來就是給老闆一刀,接着老闆跳起來又是一刀,程序員宜春嗷的一聲又是回一刀,老闆跳起來又一刀,程序員宜春嗷的一聲又是回一刀,只要老闆沒停程序員宜春就沒停,由於程序員宜春知道,就本身這曝脾氣,暴躁起來si都敢摸,確定有幾個老鐵知道....
程序員宜春就相似實例變量,每次建立對象,都會爲每一個對象分配成員變量內存空間,就像老闆來一刀,程序員宜春都會回一刀這樣子的...
咱們都知道靜態變量是屬於這個類,而不是屬因而對象,static獨立於對象。
可是各位有木有想過:靜態成員變量雖然獨立於對象,可是不表明不能夠經過對象去訪問,全部的靜態方法和靜態變量均可以經過對象訪問【只要訪問權限足夠容許就行】,不理解不要緊,來個代碼就理解了
public class StaticDemo {
static int value = 666;
public static void main(String[] args) throws Exception{
new StaticDemo().method();
}
private void method(){
int value = 123;
System.out.println(this.value);
}
}
複製代碼
猜測一下結果,我猜你的結果是123,哈哈是咩?其實
運行結果: 666
複製代碼
回過頭再去品味一下上面的那段話,你就能很是客觀明瞭了,這個思想概念要有隻是這種用法不推薦!
所以小結一下訪問靜態變量和實例變量的兩種方法:
靜態變量:
類名.靜態變量
對象.靜態變量(不推薦)
靜態方法:
類名.靜態方法
對象.靜態方法(不推薦)
static修飾的方法也叫作靜態方法,不知道各位發現咩有,其實咱們最熟悉的static靜態方法就是main方法了~小白童鞋:喔好像真的是哦~。因爲對於靜態方法來講是不屬於任何實例對象的,this指的是當前對象,由於static靜態方法不屬於任何對象,因此就談不上this了。
還有一點就是:構造方法不是靜態方法!
先看個程序吧,看看自個是否掌握了static代碼塊,下面程序代碼繼承關係爲 BaseThree——> BaseTwo——> BaseOne
BaseOne類
package com.gx.initializationblock;
public class BaseOne {
public BaseOne() {
System.out.println("BaseOne構造器");
}
{
System.out.println("BaseOne初始化塊");
System.out.println();
}
static {
System.out.println("BaseOne靜態初始化塊");
}
}
複製代碼
BaseTwo類
package com.gx.initializationblock;
public class BaseTwo extends BaseOne {
public BaseTwo() {
System.out.println("BaseTwo構造器");
}
{
System.out.println("BaseTwo初始化塊");
}
static {
System.out.println("BaseTwo靜態初始化塊");
}
}
複製代碼
BaseThree 類
package com.gx.initializationblock;
public class BaseThree extends BaseTwo {
public BaseThree() {
System.out.println("BaseThree構造器");
}
{
System.out.println("BaseThree初始化塊");
}
static {
System.out.println("BaseThree靜態初始化塊");
}
}
複製代碼
測試demo2類
package com.gx.initializationblock;
/* 注:這裏的ABC對應BaseOne、BaseTwo、BaseThree * 多個類的繼承中初始化塊、靜態初始化塊、構造器的執行順序 在繼承中,前後執行父類A的靜態塊,父類B的靜態塊,最後子類的靜態塊, 而後再執行父類A的非靜態塊和構造器,而後是B類的非靜態塊和構造器,最後執行子類的非靜態塊和構造器 */
public class Demo2 {
public static void main(String[] args) {
BaseThree baseThree = new BaseThree();
System.out.println("-----");
BaseThree baseThree2 = new BaseThree();
}
}
複製代碼
運行結果
BaseOne靜態初始化塊
BaseTwo靜態初始化塊
BaseThree靜態初始化塊
BaseOne初始化塊
BaseOne構造器
BaseTwo初始化塊
BaseTwo構造器
BaseThree初始化塊
BaseThree構造器
-----
BaseOne初始化塊
BaseOne構造器
BaseTwo初始化塊
BaseTwo構造器
BaseThree初始化塊
BaseThree構造器
複製代碼
至於static代碼塊運行結果不是很清晰的童鞋,詳細講解請看這篇Static靜態代碼塊以及各代碼塊之間的執行順序
以上僅僅是讓各位明確代碼塊之間的運行順序,顯然仍是不夠的,靜態代碼塊一般用來對靜態變量進行一些初始化操做,好比定義枚舉類,代碼以下:
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進行初始化
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;
}
} 
複製代碼
固然不只僅是枚舉這一方面,還有咱們熟悉的單例模式一樣也用到了靜態代碼塊,以下:
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
複製代碼
static變量也稱做靜態變量,靜態變量和非靜態變量的區別是:靜態變量被全部的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在建立對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
還有一點就是static成員變量的初始化順序按照定義的順序進行初始化。
靜態內部類與非靜態內部類之間存在一個最大的區別,咱們知道非靜態內部類在編譯完成以後會隱含地保存着一個引用,該引用是指向建立它的外圍內,可是靜態內部類卻沒有。沒有這個引用就意味着:
一、它的建立是不須要依賴外圍類的建立。 二、它不能使用任何外圍類的非static成員變量和方法。
代碼舉例(靜態內部類實現單例模式)
public class Singleton {
// 聲明爲 private 避免調用默認構造方法建立對象
private Singleton() {
}
// 聲明爲 private 代表靜態內部該類只能在該 Singleton 類中被訪問
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
複製代碼
當 Singleton
類加載時,靜態內部類 SingletonHolder
沒有被加載進內存。只有當調用 getUniqueInstance()
方法從而觸發 SingletonHolder.INSTANCE
時 SingletonHolder
纔會被加載,此時初始化 INSTANCE
實例,而且 JVM 能確保 INSTANCE
只被實例化一次。
這種方式不只具備延遲初始化的好處,並且由 JVM 提供了對線程安全的支持。
靜態導包格式:import static
這兩個關鍵字連用能夠指定導入某個類中的指定靜態資源,而且不須要使用類名調用類中靜態成員,能夠直接使用類中靜態成員變量和成員方法
// Math. --- 將Math中的全部靜態資源導入,這時候能夠直接使用裏面的靜態方法,而不用經過類名進行調用
// 若是隻想導入單一某個靜態方法,只須要將換成對應的方法名便可
import static java.lang.Math.;
// 換成import static java.lang.Math.max;具備同樣的效果
public class Demo {
public static void main(String[] args) {
int max = max(1,2);
System.out.println(max);
}
}
複製代碼
靜態導包在書寫代碼的時候確實能省一點代碼,能夠直接調用裏面的靜態成員,可是會影響代碼可讀性,因此開發中通常狀況下不建議這麼使用。
一、靜態只能訪問靜態。 二、非靜態既能夠訪問非靜態的,也能夠訪問靜態的。
到這裏文章本該結束了的,可是static的使用始終離不開final字眼,兩者可謂藕斷絲連,經常繁見,我以爲仍是頗有必要講講,那麼一塊兒來看看下面這個程序吧。
package Demo;
class FinalDemo {
public final double i = Math.random();
public static double t = Math.random();
}
public class DemoDemo {
public static void main(String[] args) {
FinalDemo demo1 = new FinalDemo();
FinalDemo demo2 = new FinalDemo();
System.out.println("final修飾的 i=" + demo1.i);
System.out.println("static修飾的 t=" + demo1.t);
System.out.println("final修飾的 i=" + demo2.i);
System.out.println("static修飾的 t=" + demo2.t);
System.out.println("t+1= "+ ++demo2.t );
// System.out.println( ++demo2.i );//編譯失敗
}
}
運行結果:
final修飾的 i=0.7282093281367935
static修飾的 t=0.30720545678577604
final修飾的 i=0.8106990945706758
static修飾的 t=0.30720545678577604
t+1= 1.307205456785776
複製代碼
static修飾的變量沒有發生變化是由於static做用於成員變量只是用來表示保存一份副本,其不會發生變化。怎麼理解這個副本呢?其實static修飾的在類加載的時候就加載完成了(初始化),並且只會加載一次也就是說初始化一次,因此不會發生變化!
至於final修飾的反而發生變化了?是否是巔覆你對final的見解?關於final詳細講解博主也準備好了一篇文章程序員你真的理解final關鍵字嗎?
ok,文章就先到這裏了,但願這篇文章可以幫助到你對static的認識,如有不足或者不正之處,但願諒解並歡迎批評指正!
若是本文章對你有幫助,哪怕是一點點,那就請點一個讚唄,謝謝~
參考: 《java編程思想》 baijiahao.baidu.com/s?id=160125… blog.csdn.net/qq_34337272… www.cnblogs.com/dolphin0520…
最後,歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術