java基础提升篇:Static关键字
Static變量
static關鍵字
- 在類中用static聲明的成員變量為靜態成員變量,它為該類的公用變量,在第一次使用時初始化,對于該類的所有對象來說,static成員變量只有一份。
可以通過引用或者類名訪問靜態成員
原來一個類里面的成員變量,每new一個對象,這個對象就有一份自己的成員變量,因為這些成員變量都不是靜態成員變量。對于static成員變量來說,這個成員變量只有一份,而且這一份是這個類所有的對象共享。
◆在類中,用static聲明的成員變量為靜態變量,或者叫:類屬性、類變量。
(注意:靜態變量是從屬于類,在對象里面是沒有這個屬性的;成員變量是從屬于對象的,有了對象才有那個屬性)
- 它為該類的公用變量,屬于類,被該類的所有實例共享,在類被載入時被顯示初始化。
- 對于該類所有對象來說,static成員變量只有一份.被該類的所有對象共享!!
- 可以使用“對象.類屬性”來調用。不過,一般都是用”類名.類屬性”。
- static變量置于方法區中。
- 在靜態的方法里面不可以調用非靜態的方法或變量;但是在非靜態的方法里可以調用靜態的方法或變量。
Static方法
◆用static聲明的方法為靜態方法。
- 不需要對象,就可以調用(類名.方法名)
- 在調用該方法時,不會將對象的引用傳遞給它,所以在static方法中不可訪問非static的成員。
【實例1】
student類:
測試:
靜態初始化塊
靜態初始化塊是在類被加載的時候就執行的一塊程序,并且一直存在直到程序關閉。也就是說當程序被執行,即classloader將該java程序編譯后的class文件加載后,就能執行到靜態初始化塊這段程序;當程序關閉,我的個人理解也就是java.exe進程被結束的時候,靜態初始化塊結束(例如在靜態初始化塊里對一個類的靜態變量進行賦值,該變量一直存在到程序關閉)。
下面我們來舉例說明:
public class Test {//靜態變量public static String testStatic = "testStatic";//靜態初始化塊static {System.out.println(testStatic);System.out.println("Proc begin");testStatic = "testProc";System.out.println("Proc end");}//主方法public static void main(String[] args) {System.out.println(testProc);System.out.println("main begin");System.out.println("main end");}}執行main方法輸出結果:
testStatic
Proc begin
Proc end
testProc
main begin
main end
也就是說當JVM將要執行main方法的時候,先要將Test.class加載到JVM中,此時就執行了靜態初始化塊中的程序;然后再執行執行main方法中的程序。這個例子沒有將這個類實例化,所以沒有用到構造函數。倘若需要實例化該類的時候,則構造方法的執行順序也是在靜態初始化塊之后的。
最后我們可以得出這么一個結論:Java類的執行優先順序
該類的靜態變量->該類的靜態初始化塊->該類的構造方法
若存在父類的情況下則是:
父類的靜態變量->父類的靜態初始化塊->子類的靜態變量->子類的靜態初始化塊
內存分析Static
靜態成員變量與非靜態成員變量的區別:
【示例1】
package cn.galc.test;public class Cat {/*** 靜態成員變量*/private static int sid = 0;private String name;int id;Cat(String name) {this.name = name;id = sid++;}public void info() {System.out.println("My Name is " + name + ",NO." + id);}public static void main(String[] args) {Cat.sid = 100;Cat mimi = new Cat("mimi");Cat pipi = new Cat("pipi");mimi.info();pipi.info();} }通過畫內存分析圖了解整個程序的執行過程:
執行程序的第一句話:Cat.sid = 100;時,這里的sid是一個靜態成員變量,靜態變量存放在數據區(data seg),所以首先在數據區里面分配一小塊空間sid,第一句話執行完后,sid里面裝著一個值就是100。
此時的內存布局示意圖如下所示
接下來程序執行到:
Cat mimi = new Cat(“mimi”);這里,調用Cat類的構造方法Cat(String name),構造方法的定義如下:
Cat ( String name){this.name = name;id=sid++;}調用時首先在棧內存里面分配一小塊內存mm,里面裝著可以找到在堆內存里面的Cat類的實例對象的地址,mm就是堆內存里面Cat類對象的引用對象。這個構造方法聲明有字符串類型的形參變量,所以這里把“mimi”作為實參傳遞到構造方法里面,由于字符串常量是分配在數據區存儲的,所以數據區里面多了一小塊內存用來存儲字符串“mimi”。此時的內存分布如下圖所示:
當調用構造方法時,首先在棧內存里面給形參name分配一小塊空間,名字叫name,接下來把”mimi”這個字符串作為實參傳遞給name,字符串也是一種引用類型,除了那四類8種基礎數據類型之外,其他所有的都是引用類型,所以可以認為字符串也是一個對象。所以這里相當于把”mimi”這個對象的引用傳遞給了name,所以現在name指向的是”mimi”。所以此時內存的布局如下圖所示:
接下來執行構造方法體里面的代碼:
this.name=name;這里的this指的是當前的對象,指的是堆內存里面的那只貓。這里把棧里面的name里面裝著的值傳遞給堆內存里面的cat對象的name屬性,所以此時這個name里面裝著的值也是可以找到位于數據區里面的字符串對象“mimi”的,此時這個name也是字符串對象“mimi”的一個引用對象,通過它的屬性值就可以找到位于數據區里面的字符串對象“mimi”。此時的內存分布如下圖所示:
接下來執行方法體內的另一句代碼:
id=sid++; 這里是把sid的值傳遞給id,所以id的值是100,sid傳遞完以后,自己再加1,此時sid變成了101。此時的內存布局如下圖所示。
到此,構造方法調用完畢,給這個構造方法分配的局部變量所占的內存空間全部都要消失,所以位于??臻g里面的name這塊內存消失了。棧內存里面指向數據區里面的字符串對象“mimi”的引用也消失了,此時只剩下堆內存里面的指向字符串對象“mimi”的引用沒有消失。此時的內存布局如下圖所示:
接下來執行:Cat pipi = new Cat(“pipi”);
這里是第二次調用構造方法Cat(),整個調用過程與第一次一樣,調用結束后,此時的內存布局如下圖所示:
最后兩句代碼是調用info()方法打印出來,打印結果如下:
My name is mimi,NO.100
My name is pipi,NO.101
通過這個程序,看出來了這個靜態成員變量sid的作用,它可以計數。每當有一只貓new出來的時候,就給它記一個數。讓它自己往上加1。
程序執行完后,內存中的整個布局就如上圖所示了。一直持續到main方法調用完成的前一刻。
這里調用構造方法Cat(String name) 創建出兩只貓,首先在棧內存里面分配兩小塊空間mimi和pipi,里面分別裝著可以找到這兩只貓的地址,mimi和pipi對應著堆內存里面的兩只貓的引用。這里的構造方法聲明有字符串類型的變量,字符串常量是分配在數據區里面的,所以這里會把傳過來的字符串mimi和pipi都存儲到數據區里面。所以數據區里面分配有存儲字符串mimi和pipi的兩小塊內存,里面裝著字符串“mimi”和“pipi”,字符串也是引用類型,除了那四類8種的基礎數據類型之外,其他所有的數據類型都是引用類型。所以可以認為字符串也是一個對象。
這里是new了兩只貓出來,這兩只貓都有自己的id和name屬性,所以這里的id和name都是非靜態成員變量,即沒有static修飾。所以每new出一只新貓,這只新貓都有屬于它自己的id和name,即非靜態成員變量id和name是每一個對象都有單獨的一份。但對于靜態成員變量來說,只有一份,不管new了多少個對象,哪怕不new對象,靜態成員變量在數據區也會保留一份。如這里的sid一樣,sid存放在數據區,無論new出來了多少只貓在堆內存里面,sid都只有一份,只在數據區保留一份。
靜態成員變量是屬于整個類的,它不屬于專門的某個對象。那么如何訪問這個靜態成員變量的值呢?首先第一點,任何一個對象都可以訪問這個靜態的值,訪問的時候訪問的都是同一塊內存。第二點,即便是沒有對象也可以訪問這個靜態的值,通過“類名.靜態成員變量名”來訪問這個靜態的值,所以以后看到某一個類名加上“.”再加上后面有一個東西,那么后面這個東西一定是靜態的,如”System.out”,這里就是通過類名(System類)再加上“.”來訪問這個out的,所以這個out一定是靜態的。
【示例2】
package cn.galc.test;public class Cat {/*** 這里面的sid不再是靜態成員變量了,因為沒有static修飾符,* 此時它就是類里面一個普通的非靜態成員變量,和id,name一樣,* 成為每一個new出來的對象都具有的屬性。*/private int sid = 0;private String name;int id;Cat(String name) {this.name = name;id = sid++;}public void info() {System.out.println("My Name is " + name + ",NO." + id);}public static void main(String[] args) {//Cat.sid = 100;這里不能再使用“類.靜態成員變量”的格式來訪問sid了,因為sid現在變成了非靜態的成員變量了。所以必須要把這句話注釋掉,否則無法編譯通過。Cat mimi = new Cat("mimi");Cat pipi = new Cat("pipi");mimi.info();pipi.info();} }這段代碼與上一段代碼唯一的區別是把聲明sid變量的static修飾符給去掉了,此時的sid就不再是靜態成員變量,而是非靜態成員變量了,此時每一個new出來的cat對象都會有自己單獨的sid屬性。所以這段代碼執行完成后,內存中的布局如下圖所示:
由于sid變成了非靜態成員變量,所以不再有計數的功能了。sid與id和name屬性一樣,成為每一個new出來的對象都具有的屬性,所以每一個new出來的cat都加上了一個sid屬性。由于不能再使用”類名.靜態成員對象名”的格式訪問sid,所以代碼的第一句”Cat.sid =100;”不能這樣使用,否則編譯會出錯,必須把這句話注釋掉才能編譯成功。既然無法訪問得到sid的值,所以sid的值就一直都是初始化時賦給的值0。直到調用構造方法時,執行到方法體內的代碼id=sid++;時,sid首先把自身的值0賦值給id,所以id的值是0,然后sid自己加1,所以sid變成了1。
所以靜態變量和非靜態變量的區別就在于靜態變量可以用來計數,而非靜態變量則不行。
理解了內存,就理解了一切,就理解了各種各樣的語言。所有的語言無非都是這樣:局部變量分配內存永遠在棧里面,new出來的東西分配內存永遠是在堆里,靜態的東西分配內存永遠是在數據區。剩下的代碼肯定是在代碼區。所有的語言都是這樣。
在一個靜態方法里,如果想訪問一個非靜態的成員變量,是不能直接訪問的,必須在靜態方法里new一個對象出來才能訪問。如果是加了static的成員變量,那么這個成員變量就是一個靜態的成員變量,就可以在main方法里面直接訪問了。
main方法是一個靜態的方法,main方法要執行的時候不需要new一個對象出來。
動態方法是針對于某一個對象調用的,靜態方法不會針對某一個對象來調用,沒有對象照樣可以用。所以可以使用”classname.method()”.的形式來調用靜態方法。所以想在main方法里面訪問非靜態成員變量是不可以的,想在main方法里面訪問非靜態方法也是不可以的,因為非靜態方法只能針對于某個對象來調用,沒有對象,就找不到方法的執行者了。
成員變量只有在new出一個對象來的時候才在堆內存里面分配存儲空間。局部變量在棧內存里面分配存儲空間。
靜態方法不再是針對某一個對象來調用,所以不能訪問非靜態的成員。
非靜態成員專屬于某一個對象,想訪問非靜態成員必須new一個對象出來才能訪問。
靜態的變量可以通過對象名去訪問,也可以通過類名去訪問,兩者訪問的都是同一塊內存。
總結
類只能使用類的方法和屬性,對象既可以使用類的方法,又可以使用對象的方法。
static的方法只能調用static的變量和方法,非static的方法既可以調用static的,又可以調用非static的。
參考資料
- http://www.cnblogs.com/xdp-gacl/p/3637407.html
- http://www.cnblogs.com/Qian123/p/5171407.html
- https://blog.csdn.net/deadpanda/article/details/5307932
文章有不當之處,歡迎指正,你也可以關注我的微信公眾號:好好學java,獲取優質學習資源,也可以加入QQ技術交流群:766946816,咋們來聊聊java。
總結
以上是生活随笔為你收集整理的java基础提升篇:Static关键字的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java提升篇:对象克隆(复制)
- 下一篇: Java提升篇:理解String 及 S