1. 程式人生 > >Java成員變數的初始化

Java成員變數的初始化

Java中成員變數的初始化

  • 類變數與成員變數
    在java類的初始化中,一邊只初始化一次,類的初始化主要是用以初始化類變數,即靜態變數
    在初始化的過程中存在著靜態變數與靜態塊兩部分,初始化的順序為先載入靜態變數,後加載靜態塊的內容,如下例:
public class Person{
  public static String name="張三";
  public static int age;
  static{
       age=20;
    System.out.println("初始化age");
  }
  public static String address;
  static
{     address="北京市";     age=34;   }   public static void main(String[] args) { System.out.println(name); System.out.println(age); System.out.println(address); } }

其初始化的順序如下述程式碼所示:

public class Person{
  public static String name;
  public
static int age;   public static String address;   static{     name="張三";     age=20;     System.out.println("初始化age");     address="北京市";     age=34;   }   public static void main(String[] args) { System.out.println(name); System.out.println(age); System.out
.println(address); } }

即先載入靜態變數,後加載程式碼塊的內容,對於類變數而言在宣告處進行初始化與在靜態程式碼塊中進行初始化效果是一致的。所有的類變數都是先宣告,後賦值的,並且其賦值的順序與其宣告順序一致。
也就是說在程式碼順序中即便靜態程式碼塊在前,類變數的宣告在後,也是不會出現報錯的,因為在載入的時候是先載入的類變數,後加載的靜態程式碼塊中的內容,同時在靜態程式碼塊中只能對之前宣告的類變數進行賦值,而不可以使用之前宣告的類變數,因為此時並沒有宣告,如果宣告在前方可使用

也就是說最先載入的是類變數與靜態程式碼塊中的內容,首先會載入類變數的宣告,之後再在靜態程式碼塊中按照程式碼順序進行依次載入,

    static{  
        a = 4;  
        System.out.println("lalal");  
    }  
    private static int a=6;  

    public static void main(String[] args) {
          System.out.println(a);

    }
會輸出
lalala
6

這時的載入順序如下:

static int a;
static{
a=4
System.out.println("lalal"); 
a=6
}

如下:

public class Person {  
    static{  
        a = 4;  
        //System.out.println(a);//這裡會報出錯誤。  
    }  
    private static int a;  
    public static void main(String[] args){  
        System.out.println(a);  
    }  
} 
此時的輸出結果為4

如果出現子類與父類都具有類變數的情況下

如下:

class Parent {
 // 靜態變數
 public static String p_StaticField = "父類--靜態變數";
 // 變數
 public String p_Field = "父類--變數";
 protected int i = 9;
 protected int j = 0;
 // 靜態初始化塊
 static {
  System.out.println(p_StaticField);
  System.out.println("父類--靜態初始化塊");
 }
 // 初始化塊
 {
  System.out.println(p_Field);
  System.out.println("父類--初始化塊");
 }
// 構造器
 public Parent() {
  System.out.println("父類--構造器");
  System.out.println("i=" + i + ", j=" + j);
  j = 20;
 }
}

public class SubClass extends Parent {
 // 靜態變數
 public static String s_StaticField = "子類--靜態變數";
 // 變數
 public String s_Field = "子類--變數";
 // 靜態初始化塊
 static {
  System.out.println(s_StaticField);
  System.out.println("子類--靜態初始化塊");
 }
 // 初始化塊
 {
  System.out.println(s_Field);
  System.out.println("子類--初始化塊");
 }
// 構造器
 public SubClass() {
  System.out.println("子類--構造器");
  System.out.println("i=" + i + ",j=" + j);
 }
// 程式入口
 public static void main(String[] args) {
  System.out.println("子類main方法");
  new SubClass();
 }
}
在輸出 “子類main方法”即在進入程式入口之前首先會輸出
父類--靜態變數
父類--靜態初始化塊
子類--靜態變數
子類--靜態初始化塊

當載入完類之後,進入到類物件的構造階段

對於物件的構造與類構造有相似之處,同時多出了建構函式這一部分

1 public class Person{
 2   {
 3     name="李四";
 4     age=56;
 5     System.out.println("初始化age");
 6     address="上海";
 7   }
 8   public String name="張三";
 9   public int age=29;
10   public String address="北京市";
11   public Person(){
12     name="趙六";
13     age=23;
14     address="上海市";
15   }
16 }

如上的程式碼初始化順序如下所示:

 1 public class Person{
 2   public String name;
 3   public int age;
 4   public String address;
 5   public Person(){
 6     name="李四";
 7     age=56;
 8     System.out.println("初始化age");
 9     address="上海";
10     name="張三";
11     age=29;
12     address="北京市";
13     name="趙六";
14     age=23;
15     address="上海市";
16   }
17 }

對於類中成員變數的初始化的過程相當於全部挪到了建構函式中,同時初始化的順序與之前宣告的順序一致,對同一個變數,後者的值覆蓋前者的值,所以最好name值等於趙六,age=23,address=上海

靜態變數、靜態初始化塊,變數、初始化塊初始化了順序取決於它們在類中出現的先後順序。

對比以下程式碼

public class Test1 {  

    {  
        a = 4;  
    }  

    private static int a;  

    public static void main(String[] args){  
        Test3 test3 = new Test3();//注意:這裡開始new了一個物件  
        System.out.println(test3.a);  
    }  
}  
該程式碼的輸出結果為4,因為呼叫的是test3這個物件的a成員變數
public class Test2 {  
    {  
        a = 4;  
        System.out.println(a);//這裡不會報錯,但是這條語句並不會執行  
    }  
    private static int a;  

    public static void main(String[] args){  
        System.out.println(a);  
    }  
}  
輸出0,因為呼叫Test2該類的類成員變數 且該類成員變數無初始值,預設為0
public class Test3 {  
    static{  
        a = 4;  

    }  
    private static int a;  
    public static void main(String[] args){  
        System.out.println(a);  
    }  
}  
輸出為4,通過靜態程式碼塊進行了初始化

final關鍵字的影響

一個類一旦被設定為final,則表明該類是無法被修改,也無法被繼承的。
變數被定義為final,則表明該變數有且僅有一次被賦值的機會,同時該變數必須被初始化

public class Test1 {  

    {  
        a = 4;  
    }  

    private static final int a;  

    public static void main(String[] args){  
        System.out.println(a);  
    }  
}  
對於Test1來說會出現編譯錯誤,因為在定義static final int a時沒有附初始值
public class Test2 {  

    static{  
        a = 5;  
    }  

    private static final int a;  


    public static void main(String[] args){  
        System.out.println(a);  
    }  
}  
此時a值為5,因為對於類變數在宣告時賦初值與在static程式碼塊中賦初值是一樣的效果
    static{
        a=4;
    }

       private static final int a=3;  


    public static void main(String[] args) {
          System.out.println(a);

    }

}
此時同樣會出現編譯錯誤,因為已經將宣告的a值賦值為4,不可以更改其賦值為3(注意賦值的順序)

最終在此基礎上引用下述程式碼清晰展現初始化順序

public class InitialOrderTest {
// 靜態變數
public static String staticField = "靜態變數";
// 變數
public String field = "變數";
// 靜態初始化塊
static {
System.out.println(staticField);
System.out.println("靜態初始化塊");
}
// 初始化塊
{
System.out.println(field);
System.out.println("初始化塊");
}
// 構造器
public InitialOrderTest() {
System.out.println("構造器");
}
public static void main(String[] args) {
new InitialOrderTest();
}
}
執行以上程式碼,我們會得到如下的輸出結果:
1. 靜態變數
2. 靜態初始化塊
3. 變數
4. 初始化塊
5. 構造器

這與上文中說的完全符合。那麼對於繼承情況下又會怎樣呢?我們仍然以一段測試程式碼來獲取最終結果:
class Parent {
 // 靜態變數
 public static String p_StaticField = "父類--靜態變數";
 // 變數
 public String p_Field = "父類--變數";
 protected int i = 9;
 protected int j = 0;
 // 靜態初始化塊
 static {
  System.out.println(p_StaticField);
  System.out.println("父類--靜態初始化塊");
 }
 // 初始化塊
 {
  System.out.println(p_Field);
  System.out.println("父類--初始化塊");
 }
// 構造器
 public Parent() {
  System.out.println("父類--構造器");
  System.out.println("i=" + i + ", j=" + j);
  j = 20;
 }
}

public class SubClass extends Parent {
 // 靜態變數
 public static String s_StaticField = "子類--靜態變數";
 // 變數
 public String s_Field = "子類--變數";
 // 靜態初始化塊
 static {
  System.out.println(s_StaticField);
  System.out.println("子類--靜態初始化塊");
 }
 // 初始化塊
 {
  System.out.println(s_Field);
  System.out.println("子類--初始化塊");
 }
// 構造器
 public SubClass() {
  System.out.println("子類--構造器");
  System.out.println("i=" + i + ",j=" + j);
 }
// 程式入口
 public static void main(String[] args) {
  System.out.println("子類main方法");
  new SubClass();
 }
}
執行一下上面的程式碼,結果馬上呈現在我們的眼前:
父類--靜態變數
父類--靜態初始化塊
子類--靜態變數
子類--靜態初始化塊
子類main方法
父類--變數
父類--初始化塊
父類--構造器
i=9, j=0
子類--變數
子類--初始化塊
子類--構造器
i=9,j=20

執行過程如下:
1. 訪問subClass的main方法,因為其為static方法,所以類裝載器會尋找已經編譯好的subClass.class檔案,因為其有一個基類,所以首先裝載其基類,之後裝載該類本身
2. 裝載該類的時候,其類物件會被初始化,初始化順序如前文所示,然後是該類的派生類的類物件初始化
3. 類裝載結束後,進入到main()方法,建立subClass物件
4. 由於其存在父類,因此首先進行父類物件的載入,其載入過程與類載入過程相似,先是基類的成員變數進行初始化,然後是基類的建構函式的其他部分
5. 最後是子類的成員變數進行初始化,然後是子類的建構函式其他部分。

關於類載入與類初始化要注意

  1. 類只是宣告沒有使用的時候並不會進行初始化,例如
public class ClassInitializationTest {

    public static void main(String args[]) throws InterruptedException {

        NotUsed o = null; //this class is not used, should not be initialized
        Child t = new Child(); //initializing sub class, should trigger super class initialization
        System.out.println((Object)o == (Object)t);
    }
}

/**
 * Super class to demonstrate that Super class is loaded and initialized before Subclass.
 */
class Parent {
    static { System.out.println("static block of Super class is initialized"); }
    {System.out.println("non static blocks in super class is initialized");}
}

/**
 * Java class which is not used in this program, consequently not loaded by JVM
 */
class NotUsed {
    static { System.out.println("NotUsed Class is initialized "); }
}

/**
 * Sub class of Parent, demonstrate when exactly sub class loading and initialization occurs.
 */
class Child extends Parent {
    static { System.out.println("static block of Sub class is initialized in Java "); }
    {System.out.println("non static blocks in sub class is initialized");}
}

Output:
static block of Super class is initialized
static block of Sub class is initialized in Java
non static blocks in super class is initialized
non static blocks in sub class is initialized
false
並沒有輸出NotUsed Class is initialized ,因為並沒有被使用
  1. 類初始化的,主動引用
    1. 建立類的例項(建立例項,只宣告不建立不會進行類初始化)
    2. 訪問類的靜態變數 (除常量【 被final修辭的靜態變數】 原因:常量一種特殊的變數,因為編譯器把他們當作值(value)而不是域(field)來對待。如果你的程式碼中用到了常變數(constant variable),編譯器並不會生成位元組碼來從物件中載入域的值,而是直接把這個值插入到位元組碼中。這是一種很有用的優化,但是如果你需要改變final域的值那麼每一塊用到那個域的程式碼都需要重新編譯。
    3. 訪問類的靜態方法
    4. 反射 如( Class.forName(“my.xyz.Test”) )
    5. 當初始化一個類時,發現其父類還未初始化,則先出發父類的初始化
    6. 虛擬機器啟動時,定義了main()方法的那個類先初始化
  2. 被動引用,不進行初始化
    1. 子類呼叫父類的靜態變數,子類不會被初始化。只有父類被初始化。 。 對於靜態欄位,只有直接定義這個欄位的類才會被初始化.
      2.通過陣列定義來引用類,不會觸發類的初始化
      3.訪問類的常量,不會初始化類
class SuperClass {
    static {
        System.out.println("superclass init");
    }
    public static int value = 123;
}

class SubClass extends SuperClass {
    static {
        System.out.println("subclass init");
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println(SubClass.value);// 被動應用1
        SubClass[] sca = new SubClass[10];// 被動引用2
    }
}
程式執行輸出    superclass init 
                            123

從上面的輸入結果證明了被動引用1與被動引用2

class ConstClass {
    static {
        System.out.println("ConstClass init");
    }
    public static final String HELLOWORLD = "hello world";
}

public class Test {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD);// 呼叫類常量
    }
}

程式輸出結果
hello world
從上面的輸出結果證明了被動引用3 

例項分析:

class SingleTon {
    private static SingleTon singleTon = new SingleTon();
    public static int count1;
    public static int count2 = 0;

    private SingleTon() {
        count1++;
        count2++;
    }

    public static SingleTon getInstance() {
        return singleTon;
    }
}

public class Test {
    public static void main(String[] args) {
        SingleTon singleTon = SingleTon.getInstance();
        System.out.println("count1=" + singleTon.count1);
        System.out.println("count2=" + singleTon.count2);
    }
}
分析

1SingleTon singleTon = SingleTon.getInstance();呼叫了類的SingleTon呼叫了類的靜態方法,觸發類的初始化 
2類載入的時候在準備過程中為類的靜態變數分配記憶體並初始化預設值 singleton=null count1=0,count2=0 
3類初始化化,為類的靜態變數賦值和執行靜態程式碼快。**singleton賦值為new SingleTon()呼叫類的構造方法** 
4呼叫類的構造方法後count=1;count2=1 
5繼續為count1與count2賦值,此時count1沒有賦值操作,所有count1為1,但是count2執行賦值操作就變為0

總結:

  1. 先明確什麼時候初始化,什麼時候不要初始化
  2. 再明確初始化順序