static關鍵字

原文地址:http://www.cnblogs.com/xdp-gacl/p/3637407.html

重點是程序執行過程中內存的分配情況:
棧:局部變量
堆:new 出來的對象
據存儲區:static變量,字符串常量

一、static關鍵字

  

  原來一個類裏面的成員變量,每new一個對象,這個對象就有一份自己的成員變量,因爲這些成員變量都不是靜態成員變量。對於static成員變量來說,這個成員變量只有一份,而且這一份是這個類所有的對象共享。

1.1.靜態成員變量與非靜態成員變量的區別

  以下面的例子爲例說明

複製代碼
 1 package cn.galc.test;
 2 
 3 public class Cat {
 4 
 5     /**
 6      * 靜態成員變量
 7      */
 8     private static int sid = 0;
 9 
10     private String name;
11 
12     int id;
13 
14     Cat(String name) {
15         this.name = name;
16         id = sid++;
17     }
18 
19     public void info() {
20         System.out.println("My Name is " + name + ",NO." + id);
21     }
22 
23     public static void main(String[] args) {
24         Cat.sid = 100;
25         Cat mimi = new Cat("mimi");
26         Cat pipi = new Cat("pipi");
27         mimi.info();
28         pipi.info();
29     }
30 }
複製代碼

通過畫內存分析圖瞭解整個程序的執行過程

  執行程序的第一句話: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的值是100sid傳遞完以後,自己再加1,此時sid變成了101。此時的內存佈局如下圖所示。

  

  到此,構造方法調用完畢,給這個構造方法分配的局部變量所佔的內存空間全部都要消失,所以位於棧空間裏面的name這塊內存消失了。棧內存裏面指向數據區裏面的字符串對象“mimi”的引用也消失了,此時只剩下堆內存裏面的指向字符串對象“mimi”的引用沒有消失。此時的內存佈局如下圖所示:

  

  接下來執行:Cat  pipi = new Cat(“pipi”);

  這裏是第二次調用構造方法Cat(),整個調用過程與第一次一樣,調用結束後,此時的內存佈局如下圖所示:

  

  最後兩句代碼是調用info()方法打印出來,打印結果如下:

  

  通過這個程序,看出來了這個靜態成員變量sid的作用,它可以計數。每當有一隻貓new出來的時候,就給它記一個數。讓它自己往上加1

  程序執行完後,內存中的整個佈局就如上圖所示了。一直持續到main方法調用完成的前一刻。

  這裏調用構造方法Cat(String name) 創建出兩隻貓,首先在棧內存裏面分配兩小塊空間mimipipi,裏面分別裝着可以找到這兩隻貓的地址,mimipipi對應着堆內存裏面的兩隻貓的引用。這裏的構造方法聲明有字符串類型的變量,字符串常量是分配在數據區裏面的,所以這裏會把傳過來的字符串mimipipi都存儲到數據區裏面。所以數據區裏面分配有存儲字符串mimipipi的兩小塊內存,裏面裝着字符串“mimi”和“pipi”,字符串也是引用類型,除了那四類8種的基礎數據類型之外,其他所有的數據類型都是引用類型。所以可以認爲字符串也是一個對象。

  這裏是new了兩隻貓出來,這兩隻貓都有自己的idname屬性,所以這裏的idname都是非靜態成員變量,即沒有static修飾。所以每new出一隻新貓,這隻新貓都有屬於它自己的idname,即非靜態成員變量idname是每一個對象都有單獨的一份。但對於靜態成員變量來說,只有一份,不管new了多少個對象,哪怕不new對象,靜態成員變量在數據區也會保留一份。如這裏的sid一樣,sid存放在數據區,無論new出來了多少隻貓在堆內存裏面,sid都只有一份,只在數據區保留一份。

  靜態成員變量是屬於整個類的,它不屬於專門的某個對象。那麼如何訪問這個靜態成員變量的值呢?首先第一點,任何一個對象都可以訪問這個靜態的值,訪問的時候訪問的都是同一塊內存。第二點,即便是沒有對象也可以訪問這個靜態的值,通過“類名.靜態成員變量名”來訪問這個靜態的值,所以以後看到某一個類名加上“.”再加上後面有一個東西,那麼後面這個東西一定是靜態的,如”System.out”,這裏就是通過類名(System類)再加上“.”來訪問這個out的,所以這個out一定是靜態的。

再看下面的這段代碼

複製代碼
 1 package cn.galc.test;
 2 
 3 public class Cat {
 4 
 5     /**
 6      * 這裏面的sid不再是靜態成員變量了,因爲沒有static修飾符,
 7      * 此時它就是類裏面一個普通的非靜態成員變量,和id,name一樣,
 8      * 成爲每一個new出來的對象都具有的屬性。
 9      */
10     private  int sid = 0;
11 
12     private String name;
13 
14     int id;
15 
16     Cat(String name) {
17         this.name = name;
18         id = sid++;
19     }
20 
21     public void info() {
22         System.out.println("My Name is " + name + ",NO." + id);
23     }
24 
25     public static void main(String[] args) {
26         //Cat.sid = 100;這裏不能再使用“類.靜態成員變量”的格式來訪問sid了,因爲sid現在變成了非靜態的成員變量了。所以必須要把這句話註釋掉,否則無法編譯通過。
27         Cat mimi = new Cat("mimi");
28         Cat pipi = new Cat("pipi");
29         mimi.info();
30         pipi.info();
31     }
32 }
複製代碼

  這段代碼與上一段代碼唯一的區別是把聲明sid變量的static修飾符給去掉了,此時的sid就不再是靜態成員變量,而是非靜態成員變量了,此時每一個new出來的cat對象都會有自己單獨的sid屬性。所以這段代碼執行完成後,內存中的佈局如下圖所示:

  

  由於sid變成了非靜態成員變量,所以不再有計數的功能了。sididname屬性一樣,成爲每一個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一個對象出來才能訪問。

  靜態的變量可以通過對象名去訪問,也可以通過類名去訪問,兩者訪問的都是同一塊內存。

發佈了48 篇原創文章 · 獲贊 5 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章