一、static關鍵字
原來一個類裡面的成員變數,每new一個對象,這個對象就有一份自己的成員變數,因為這些成員變數都不是靜態成員變數。對於static成員變數來說,這個成員變數只有一份,而且這一份是這個類所有的對象共用。
1.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。此時的記憶體布局如下圖所示。
到此,構造方法調用完畢,給這個構造方法分配的局部變數所佔的記憶體空間全部都要消失,所以位於棧空間裡面的name這塊記憶體消失了。棧記憶體裡面指向資料區裡面的字串對象“mimi”的引用也消失了,此時只剩下堆記憶體裡面的指向字串對象“mimi”的引用沒有消失。此時的記憶體布局如下圖所示:
接下來執行:Cat pipi = new Cat(“pipi”);
這裡是第二次調用構造方法Cat(),整個調用過程與第一次一樣,調用結束後,此時的記憶體布局如下圖所示:
最後兩句代碼是調用info()方法列印出來,列印結果如下:
通過這個程式,看出來了這個靜態成員變數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一定是靜態。
再看下面的這段代碼
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一個對象出來才能訪問。
靜態變數可以通過對象名去訪問,也可以通過類名去訪問,兩者訪問的都是同一塊記憶體。
以上就是本文的全部內容,資訊量很大,需要大家耐心閱讀,從而真正的學會java static關鍵字。