靜態成員變數與非靜態成員變數的區別
以下面的例子為例說明
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一定是靜態的。
如果一個類別成員被宣告為static,它就能夠在類別的任何物件建立之前被訪問,而不必引用任何物件。 static 成員最常見的例子是main( ) 的。因為在程式開始執行時必須呼叫main() ,所以它被宣告為static。
宣告為static的變數實質上就是全域變數。當宣告一個物件時,並不會產生static變數的拷貝,而是該類別所有的實例變數共用同一個static變量,例如:聲明一個static的變數count作為new一個類別實例的計數。聲明為static的方法有以下幾個限制:
(1)、它們只能呼叫其他的static 方法。
(2)、它們只能存取static資料。
(3)、它們不能以任何方式引用this 或super。
如果你需要透過計算來初始化你的static變量,你可以宣告一個static區塊,Static 區塊只在該類別載入時執行一次。下面的例子顯示
的類別有一個static方法,一些static變量,以及一個static 初始化區塊:public class UserStatic { static int a = 3; static int b; static void meth(int x) { System.out.println("x = " + x); System.out.println("a = " + a); System.out.println("b = " + b); } static { System.out.println("Static block initialized."); b = a * 4; } public static void main(String args[]) { meth(42); } }一旦UseStatic 類別被裝載,所有的static語句被運行。首先,a被設定為3,接著static 區塊執行(列印一則訊息),最後,b被初始化為a*4 或12。然後呼叫main(),main() 呼叫meth() ,把值42傳遞給x。 3個println ( ) 語句引用兩個static變數a和b,以及局部變數x 。
注意:在一個static 方法中引用任何實例變數都是非法的。
下面是該程式的輸出:
Static block initialized. x = 42 a = 3 b = 12
在定義它們的類別的外面,static 方法和變數能獨立於任何物件而被使用。這樣,你只要在類別的名字後面加點號(.)運算子即可。例如,如果你希望從類別外面呼叫一個static方法,你可以使用下面通用的格式:
classname.method( )
這裡,classname 是類別的名字,在該類別中定義static方法。可以看到,這種格式與透過物件參考變數呼叫非static方法的格式類似。一個static變數可以以同樣的格式來存取――類別名稱加點號運算子。這就是Java 如何實作全域功能和全域變數的控製版本。
總結:
(1)、static成員是無法被其所在class所建立的實例存取的。
(2)、如果不加static修飾的成員是物件成員,也就是歸每個物件所有的。
(3)、加static修飾的成員是類別成員,就是可以由一個類別直接調用,為所有物件共有的。
Java Static:作為修飾符, 可以用來修飾變數、方法、程式碼區塊(但絕對不能修飾類別)。
(1)、修飾變數:
類別的所有物件共同擁有的屬性,也稱為類別變數。這類似於C語言中的全域變數。類別變數在類別載入的時候初始化,而且只初始化一次。在程式中任何物件對靜態變數做修改,其他物件看到的是修改後的值。因此類別變數可以用作計數器。另外,Java Static變數可以用類別名稱直接訪問,而不必需要物件。
(2)、修飾方法:
類別的所有物件共同擁有的一個功能,稱為靜態方法。靜態方法也可以用類別名稱直接訪問,而不必需要物件。所以在靜態方法裡不能直接存取非靜態變數和非靜態方法,在Static方法裡不能出現this或super等關鍵字。
(3)、修飾Java程式碼區塊:
用static去修飾類別裡面的一個獨立的程式碼區塊,稱為靜態程式碼區塊。靜態程式碼區塊在類別第一次載入的時候執行,而且只執行一次。靜態程式碼區塊沒有名字,因此不能明確調用,而只有在類別載入的時候由虛擬機器來調用。它主要用來完成一些初始化操作。
(4)、說說類別載入:
JVM在第一次使用一個類別時,會到classpath所指定的路徑裡去找這個類別所對應的字節碼檔案, 並讀進JVM保存起來,這個過程稱之為類別載入。
可見,無論是變量,方法,還是程式碼區塊,只要用static修飾,就是在類別被載入時就已經"準備好了",也就是可以被使用或已經被執行。都可以脫離物件而執行。反之,如果沒有static,則必須透過物件來存取。