java基礎(五)-----關鍵字static


 

在Java中並不存在全局變量的概念,但是我們可以通過static來實現一個“偽全局”的概念,在Java中static表示“全局”或者“靜態”的意思,用來修飾成員變量和成員方法,當然也可以修飾代碼塊。

Static變量

  • 在類中用static聲明的成員變量為靜態成員變量,它為該類的公用變量,在第一次使用時初始化,對於該類的所有對象來說,static成員變量只有一份。

  • 可以通過引用或者類名訪問靜態成員

  • 在類中,用static聲明的成員變量為靜態變量,或者叫:類屬性、類變量。

(注意:靜態變量是從屬於類,在對象里面是沒有這個屬性的;成員變量是從屬於對象的,有了對象才有那個屬性)

 

  • 它為該類的公用變量,屬於類,被該類的所有實例共享,在類被載入時被顯示初始化。

  • 對於該類所有對象來說,static成員變量只有一份.被該類的所有對象共享!!

  • 可以使用“對象.類屬性”來調用。不過,一般都是用"類名.類屬性”。

  • static變量置於方法區中。

  • 在靜態的方法里面不可以調用非靜態的方法或變量;但是在非靜態的方法里可以調用靜態的方法或變量。

Static方法

◆用static聲明的方法為靜態方法。

  • 不需要對象,就可以調用(類名.方法名

  • 在調用該方法時,不會將對象的引用傳遞給它,所以在static方法中不可訪問非static的成員

靜態初始化塊

  靜態初始化塊是在類被加載的時候就執行的一塊程序,並且一直存在直到程序關閉。也就是說當程序被執行,即classloader將該java程序編譯后的class文件加載后,就能執行到靜態初始化塊這段程序;當程序關閉,我的個人理解也就是java.exe進程被結束的時候,靜態初始化塊結束(例如在靜態初始化塊里對一個類的靜態變量進行賦值,該變量一直存在到程序關閉)。

下面我們來舉例說明:

 1 public class Test {
 2     //靜態變量
 3     public static String testStatic = "testStatic";
 4     //靜態初始化塊
 5     static {
 6           System.out.println(testStatic);
 7           System.out.println("Proc begin");
 8           testStatic = "testProc";
 9           System.out.println("Proc end");
10     }
11     //主方法
12     public static void main(String[] args) {
13           System.out.println(testProc);
14           System.out.println("main begin");
15           System.out.println("main end");
16     }
17 }

執行main方法輸出結果:

1 testStatic
2 Proc begin
3 Proc end
4 testProc
5 main begin
6 main end

也就是說當JVM將要執行main方法的時候,先要將Test.class加載到JVM中,此時就執行了靜態初始化塊中的程序;然后再執行執行main方法中的程序。這個例子沒有將這個類實例化,所以沒有用到構造函數。倘若需要實例化該類的時候,則構造方法的執行順序也是在靜態初始化塊之后的。

最后我們可以得出這么一個結論:Java類的執行優先順序

該類的靜態變量->該類的靜態初始化塊->該類的構造方法

若存在父類的情況下則是:

父類的靜態變量->父類的靜態初始化塊->子類的靜態變量->子類的靜態初始化塊

內存分析Static

靜態成員變量:

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

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

執行程序的第一句話:Cat.sid = 100;時,這里的sid是一個靜態成員變量,靜態變量存放在數據區(data seg),所以首先在數據區里面分配一小塊空間sid,第一句話執行完后,sid里面裝着一個值就是100。

此時的內存布局示意圖如下所示

接下來程序執行到:

1 Cat  mimi = new Cat(“mimi”);

這里,調用Cat類的構造方法Cat(String name),構造方法的定義如下:

1 Cat ( String name){
2   this.name = name;
3   id=sid++;
4 }

  調用時首先在棧內存里面分配一小塊內存mm,里面裝着可以找到在堆內存里面的Cat類的實例對象的地址,mm就是堆內存里面Cat類對象的引用對象。這個構造方法聲明有字符串類型的形參變量,所以這里把“mimi”作為實參傳遞到構造方法里面,由於字符串常量是分配在數據區存儲的,所以數據區里面多了一小塊內存用來存儲字符串“mimi”。此時的內存分布如下圖所示:

  當調用構造方法時,首先在棧內存里面給形參name分配一小塊空間,名字叫name,接下來把”mimi”這個字符串作為實參傳遞給name,字符串也是一種引用類型,除了那四類8種基礎數據類型之外,其他所有的都是引用類型,所以可以認為字符串也是一個對象。所以這里相當於把”mimi”這個對象的引用傳遞給了name,所以現在name指向的是”mimi”。所以此時內存的布局如下圖所示:

接下來執行構造方法體里面的代碼:

1 this.name=name;

  這里的this指的是當前的對象,指的是堆內存里面的那只貓。這里把棧里面的name里面裝着的值傳遞給堆內存里面的cat對象的name屬性,所以此時這個name里面裝着的值也是可以找到位於數據區里面的字符串對象“mimi”的,此時這個name也是字符串對象“mimi”的一個引用對象,通過它的屬性值就可以找到位於數據區里面的字符串對象“mimi”。此時的內存分布如下圖所示:

接下來執行方法體內的另一句代碼:

1 id=sid++;

這里是把sid的值傳遞給id,所以id的值是100,sid傳遞完以后,自己再加1,此時sid變成了101。此時的內存布局如下圖所示。

 

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

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

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

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

1 My name is mimi,NO.100
2 My name is pipi,NO.101

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

  這里調用構造方法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,則必須要依賴於對象實例。
  • static的方法只能調用static的變量和方法,非static的方法既可以調用static的,又可以調用非static的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM