1、static表明着什麼java
在Java中並不存在全局變量的概念,可是咱們能夠經過static來實現一個「僞全局」的概念,在Java中static表示「全局」或者「靜態」的意思,用來修飾成員變量和成員方法,固然也能夠修飾代碼塊。面試
Java把內存分爲棧內存和堆內存,其中棧內存用來存放一些基本類型的變量、數組和對象的引用,堆內存主要存放一些對象。在JVM加載一個類的時候,若該類存在static修飾的成員變量和成員方法,則會爲這些成員變量和成員方法在固定的位置開闢一個固定大小的內存區域(只要這個類被加載,Java虛擬機就能根據類名在運行時數據區的方法區內定找到他們),有了這些「固定」的特性,那麼JVM就能夠很是方便地訪問他們。同時若是靜態的成員變量和成員方法不出做用域的話,它們的句柄都會保持不變。同時static所蘊含「靜態」的概念表示着它是不可恢復的,即在那個地方,你修改了,他是不會變回原樣的,你清理了,他就不會回來了。數組
同時被static修飾的成員變量和成員方法是獨立於該類的,它不依賴於某個特定的實例變量,也就是說它被該類的全部實例共享。全部實例的引用都指向同一個地方,任何一個實例對其的修改都會致使其餘實例的變化。性能
public class User { private static int userNumber = 0 ; public User(){ userNumber ++; } public static void main(String[] args) { User user1 = new User(); User user2 = new User(); System.out.println("user1 userNumber:" + User.userNumber); System.out.println("user2 userNumber:" + User.userNumber); } } ------------ Output: user1 userNumber:2 user2 userNumber:2
2、怎麼使用static優化
static能夠用於修飾成員變量和成員方法,咱們將其稱之爲靜態變量和靜態方法,直接經過類名來進行訪問。this
ClassName.propertyNamespa
ClassName.methodName(……)code
static修飾的代碼塊表示靜態代碼塊,當JVM裝載類的時候,就會執行這塊代碼,其用處很是大。對象
一、static變量blog
static修飾的變量咱們稱之爲靜態變量,沒有用static修飾的變量稱之爲實例變量,他們二者的區別是:
靜態變量是隨着類加載時被完成初始化的,它在內存中僅有一個,且JVM也只會爲它分配一次內存,同時類全部的實例都共享靜態變量,能夠直接經過類名來訪問它。可是實例變量則不一樣,它是伴隨着實例的,每建立一個實例就會產生一個實例變量,它與該實例同生共死。
因此咱們通常在這兩種狀況下使用靜態變量:對象之間共享數據、訪問方便。
public class TestStatic { public static int count = 0; public static void main(String[] args){ TestStatic test1=new TestStatic(); System.out.println(test1.count); TestStatic test2=new TestStatic(); test2.count++; System.out.println(test1.count+" "+test2.count+" "+TestStatic.count); } }
輸出結果:
0 1 1 1
可見,static變量並非所在類的某個具體對象全部,而是該類的全部對象所共有的,靜態變量既能被對象調用,也能直接拿類來調用。
二、static方法
static方法通常稱做靜態方法,因爲靜態方法不依賴於任何對象就能夠進行訪問,所以對於靜態方法來講,是沒有this的,由於它不依附於任何對象,既然都沒有對象,就談不上this了。而且因爲這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,由於非靜態成員方法/變量都是必須依賴具體的對象纔可以被調用。
可是要注意的是,雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變量,可是在非靜態成員方法中是能夠訪問靜態成員方法/變量的。
由於static方法獨立於任何實例,所以static方法必須被實現,而不能是抽象的abstract。
總結一下,對於靜態方法須要注意如下幾點:
(1)它們僅能調用其餘的static 方法。
(2)它們只能訪問static數據。
(3)它們不能以任何方式引用this 或super。
舉個簡單的例子:
在上面的代碼中,因爲print2方法是獨立於對象存在的,能夠直接用過類名調用。假如說能夠在靜態方法中訪問非靜態方法/變量的話,那麼若是在main方法中有下面一條語句:
MyObject.print2();
此時對象都沒有,str2根本就不存在,因此就會產生矛盾了。一樣對於方法也是同樣,因爲你沒法預知在print1方法中是否訪問了非靜態成員變量,因此也禁止在靜態成員方法中訪問非靜態成員方法。
而對於非靜態成員方法,它訪問靜態成員方法/變量顯然是毫無限制的。
所以,若是說想在不建立對象的狀況下調用某個方法,就能夠將這個方法設置爲static。咱們最多見的static方法就是main方法,至於爲何main方法必須是static的,如今就很清楚了。由於程序在執行main方法的時候沒有建立任何對象,所以只有經過類名來訪問。
另外記住,即便沒有顯示地聲明爲static,類的構造器實際上也是靜態方法。
三、static代碼塊
static關鍵字還有一個比較關鍵的做用就是 用來造成靜態代碼塊以優化程序性能。static塊能夠置於類中的任何地方,類中能夠有多個static塊。在類初次被加載的時候,會按照static塊的順序來執行每一個static塊,而且只會執行一次。
爲何說static塊能夠用來優化程序性能,是由於它的特性:只會在類加載的時候執行一次。下面看個例子:
class Person{ private Date birthDate; public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBornBoomer() { Date startDate = Date.valueOf("1946"); Date endDate = Date.valueOf("1964"); return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0; } }
isBornBoomer是用來判斷這我的是不是1946-1964年出生的,而每次isBornBoomer被調用的時候,都會生成startDate和birthDate兩個對象,形成了空間浪費,若是改爲這樣效率會更好:
class Person{ private Date birthDate; private static Date startDate,endDate; static{ startDate = Date.valueOf("1946"); endDate = Date.valueOf("1964"); } public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBornBoomer() { return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0; } }
所以,不少時候會將一些只須要進行一次的初始化操做都放在static代碼塊中進行。
3、static關鍵字的誤區
一、static關鍵字會改變類中成員的訪問權限嗎?
有些初學的朋友會將java中的static與C/C++中的static關鍵字的功能混淆了。在這裏只須要記住一點:與C/C++中的static不一樣,Java中的static關鍵字不會影響到變量或者方法的做用域。在Java中可以影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。看下面的例子就明白了:
提示錯誤"Person.age 不可視",這說明static關鍵字並不會改變變量和方法的訪問權限。
二、能經過this訪問靜態成員變量嗎?
雖然對於靜態方法來講沒有this,那麼在非靜態方法中可以經過this訪問靜態成員變量嗎?先看下面的一個例子,這段代碼輸出的結果是什麼?
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關聯,因此輸出結果是33。在這裏永遠要記住一點:靜態成員變量雖然獨立於對象,可是不表明不能夠經過對象去訪問,全部的靜態方法和靜態變量均可以經過對象訪問(只要訪問權限足夠)。
三、static能做用於局部變量麼?
在C/C++中static是能夠做用域局部變量的,可是在Java中切記:static是不容許用來修飾局部變量。不要問爲何,這是Java語法的規定。
四、static和final一塊用表示什麼?
static final用來修飾成員變量和成員方法,可簡單理解爲「全局常量」!
對於變量,表示一旦給值就不可修改,而且經過類名能夠訪問。
對於方法,表示不可覆蓋,而且能夠經過類名直接訪問。
4、常見的筆試面試題
下面列舉一些面試筆試中常常遇到的關於static關鍵字的題目
一、下面這段代碼的輸出結果是什麼?
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
至於爲何是這個結果,咱們先不討論,先來想一下這段代碼具體的執行過程,在執行開始,先要尋找到main方法,由於main方法是程序的入口,可是在執行main方法以前,必須先加載Test類,而在加載Test類的時候發現Test類繼承自Base類,所以會轉去先加載Base類,在加載Base類的時候,發現有static塊,便執行了static塊。在Base類加載完成以後,便繼續加載Test類,而後發現Test類中也有static塊,便執行static塊。在加載完所需的類以後,便開始執行main方法。在main方法中執行new Test()的時候會先調用父類的構造器,而後再調用自身的構造器。所以,便出現了上面的輸出結果。
二、這段代碼的輸出結果是什麼?
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
相似地,咱們仍是來想一下這段代碼的具體執行過程。首先加載Test類,所以會執行Test類中的static塊。接着執行new MyClass(),而MyClass類尚未被加載,所以須要加載MyClass類。在加載MyClass類的時候,發現MyClass類繼承自Test類,可是因爲Test類已經被加載了,因此只須要加載MyClass類,那麼就會執行MyClass類的中的static塊。在加載完以後,就經過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,所以會執行Test中的Person person = new Person(),而Person類尚未被加載過,所以會先加載Person類並執行Person類中的static塊,接着執行父類的構造器,完成了父類的初始化,而後就來初始化自身了,所以會接着執行MyClass中的Person person = new Person(),最後執行MyClass的構造器。
三、這段代碼的輸出結果是什麼?
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塊能夠出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),而且執行是按照static塊的順序執行的。
四、靜態代碼塊的初始化順序
class Parent { static String name = "hello"; { System.out.println("parent block"); } static { System.out.println("parent static block"); } public Parent() { System.out.println("parent constructor"); } } class Child extends Parent { static String childName = "hello"; { System.out.println("child block"); } static { System.out.println("child static block"); } public Child() { System.out.println("child constructor"); } } public class TestStatic { public static void main(String[] args) { new Child();// 語句(*) } }
問題:當執行完語句(*)時,打印結果是什麼順序?爲何?
輸出結果:
parent static block child static block parent block parent constructor child block child constructor
分析:當執行new Child()時,它首先去看父類裏面有沒有靜態代碼塊,若是有,它先去執行父類裏面靜態代碼塊裏面的內容,當父類的靜態代碼塊裏面的內容執行完畢以後,接着去執行子類(本身這個類)裏面的靜態代碼塊,當子類的靜態代碼塊執行完畢以後,它接着又去看父類有沒有非靜態代碼塊,若是有就執行父類的非靜態代碼塊,父類的非靜態代碼塊執行完畢,接着執行父類的構造方法;父類的構造方法執行完畢以後,它接着去看子類有沒有非靜態代碼塊,若是有就執行子類的非靜態代碼塊。子類的非靜態代碼塊執行完畢再去執行子類的構造方法,這個就是一個對象的初始化順序。
總結:
對象的初始化順序:首先執行父類靜態的內容,父類靜態的內容執行完畢後,接着去執行子類的靜態的內容,當子類的靜態內容執行完畢以後,再去看父類有沒有非靜態代碼塊,若是有就執行父類的非靜態代碼塊,父類的非靜態代碼塊執行完畢,接着執行父類的構造方法;父類的構造方法執行完畢以後,它接着去看子類有沒有非靜態代碼塊,若是有就執行子類的非靜態代碼塊。子類的非靜態代碼塊執行完畢再去執行子類的構造方法。總之一句話,靜態代碼塊內容先執行,接着執行父類非靜態代碼塊和構造方法,而後執行子類非靜態代碼塊和構造方法。
注意:子類的構造方法,無論這個構造方法帶不帶參數,默認的它都會先去尋找父類的不帶參數的構造方法。若是父類沒有不帶參數的構造方法,那麼子類必須用supper關鍵子來調用父類帶參數的構造方法,不然編譯不能經過。