一、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關鍵字。