Java初始化理解與總結

Java的初始化可以分為兩個部分:
(a)類的初始化
(b)對象的創建
(a)類的初始化


**一、概念介紹: ** 一個類(class)要被使用必須經過裝載,連接,初始化這樣的過程。

  • 在裝載階段,類裝載器會把編譯形成的class文件載入內存,創建類相關的Class對象,這個Class對象封裝了我們要使用的類的類型信息。
  • 連接階段又可以分為三個子步驟:驗證、準備和解析。
  • 驗證就是要確保java類型數據格式 的正確性,并適于JVM使用。
  • 準備階段,JVM為靜態變量分配內存空間,并設置默認值,注意,這里是設置默認值,比如說int型的變量會被賦予默認值0 。在這個階段,JVM可能還會為一些數據結構分配內存,目的是提高運行程序的性能,比如說方法表。
  • 解析過程就是在類型的常量池中尋找類、接口、字段和方法的符號引用,把這些符號引用替換成直接引用。這個階段可以被推遲到初始化之后,當程序運行的過程中真正使用某個符號引用的時候 再去解析它。
  • 類的初始化:
  • 初始化類,是指初始化static靜態變量和執行static靜態代碼塊。
  • 初始化接口,是指初始化定義在該接口中的filed。

二、類的初始化條件:
類會在首次被“主動使用”時執行初始化,以下情況第一次發生之前類會被初始化:

  1. 創建類的實例;
  2. 調用類的靜態方法;
  3. 調用類的靜態變量,并且該變量不是一個常變量
  4. 為類的靜態字段賦值;
  5. 在頂層類中執行assert語句;(?)
  6. 調用類Class及包java.lang.reflect中的某些反射方法;

JLS嚴格的說明:在任何其他的情況下,都不會對類或接口進行初始化。

三、類的初始化規則:

  1. 類初始化時,該類的父類將首先被初始化,此過程一直遞歸到 java.lang.Object為止。但是父類實現的接口并不會被初始化。
class Parent implements J{
    { System.out.println("父類初始化塊"); }  
    static{
        System.out.println("父類靜態初始化塊");  
    }
}
class Child extends Parent implements I{
    { System.out.println("子類初始化塊"); }  
    static{
        System.out.println("子類靜態初始化塊");  
    }
}
interface I {  
    int i = Test.out("interface : i", 1);  
}  
interface J {  
    int j = Test.out("interface : j", 2);  
}  
public class Test { 
    static int out(String s, int i) {  
        System.out.println(s + "=" + i);  
        return i;  
    }  
    public static void main(String [] args){
        new Child();
    }
}
接口只有被用到時才會被初始化
輸出: 
父類靜態初始化塊
子類靜態初始化塊
父類初始化塊
子類初始化塊
  • 接口初始化時,只會初始化該接口本身,并不會初始化它的父接口。
interface I {  
    int i = Test.out("interface : i", 1);  
    int ii = Test.out("interface : ii", 11);  
}  
interface J extends I{  
    int j = Test.out("interface : j", 2);  
    int jj = Test.out("interface : jj", 22);  
}  
public class Test { 
    static int out(String s, int i) {  
        System.out.println(s + "=" + i);  
        return i;  
    }  
    public static void main(String [] args){
        System.out.println(J.j);  
    }
}
輸出:
interface : j=2
interface : jj=22
2
  • 如果類的初始化是由于訪問靜態域而觸發,那么只有真正定義該靜態域的類才被初始化,而不會觸發超類的初始化或者子類的初始化即使靜態域被子類或子接口或者它的實現類所引用。
    示例1:(如上所述)
class Parent{
    static int p = 10;
    static{
        System.out.println("父類靜態初始化塊");  
    }
}
class Child extends Parent{ 
    static int c = 20; 
    static{
        System.out.println("子類靜態初始化塊");  
    }
} 
public class Test { 
    public static void main(String [] args){
        System.out.println(Child.p);    //靜態域p被子類引用
    }
}
父類靜態初始化塊
10

示例2:(滿足類的初始化條件,父類也會被初始化,與示例1不同)

public class Test { 
    public static void main(String [] args){
        System.out.println(Child.c); 
    }
}
父類靜態初始化塊
子類靜態初始化塊
20
  • 如果一個靜態變量是編譯時常量,則對它的引用不會引起定義它的類的初始化。
    示例1:
class Parent{
    static{
        System.out.println("父類靜態初始化塊");  
    }
}
class Child extends Parent{
    static final int x = 2005;  
    static{
        System.out.println("子類靜態初始化塊");  
    }
} 
public class Test { 
    public static void main(String [] args){
        System.out.println(Child.x); 
    }
}
輸出:
2005

示例2:(I.i是一個編譯時常量,因此它不會引起I被初始化。)

interface I {  
    int i = 1;  
    int ii = Test.out("ii", 2);  
}  
public class Test { 
    static int out(String s, int i) {  
        System.out.println(s + "=" + i);  
        return i;  
    }  
    public static void main(String [] args){
        System.out.println(I.i);  
    }
}
1
  • 初始化類是指初始化static靜態變量和執行static靜態代碼塊,所以只會進行一次。
    初始化接口也只會進行一次。
    示例1:
class Child extends Parent{ 
    static int c = 20; 
    static{
        System.out.println("子類靜態初始化塊");  
    }
} 
public class Test { 
    public static void main(String [] args){
        System.out.println(Child.c); 
        System.out.println(Child.c); 
    }
}
父類靜態初始化塊
子類靜態初始化塊
20
20

示例2:

interface J{   
    int j = Test.out("j", 3);  
    int jj = Test.out("jj", 4);  
}    
public class Test { 
    static int out(String s, int i) {  
        System.out.println(s + "=" + i);  
        return i;  
    }  
    public static void main(String [] args){
        System.out.println(J.j);  
        System.out.println(J.j);  
    }
}
j=3
jj=4
3
3

(b)對象創建過程中的Java初始化

一. 對象的創建過程總結

假設有個名為Dog的類:

  1. 當首次創建類型為Dog的對象時,或者Dog類的靜態方法/靜態域首次被訪問時,java解釋器必須查找類路徑,以定位Dog.class文件。
  2. 然后載入Dog.class(這將創建一個Class對象),有關靜態初始化的所有動作都會執行。因此,靜態初始化只在class對象首次加載的時候進行一次。
  3. 當用new Dog()創建對象的時候,首先將在堆上為Dog對象分配足夠的存儲空間。
  4. 這塊存儲空間會被清零,這就自動地將Dog對象中的所有基本類型數據都設置成了默認值(對數字來說就是0,對布爾型與字符型也相同),而引用則被設置成了null。
  5. 執行所有出現于字段定義處的初始化動作。
  6. 執行構造器。
    ---《Thinking in java》

二. 對象創建過程中初始化順序
父靜態成員>子靜態成員>父普通成員初始化>父構造>子普通成員初始化>子構造.
( 靜態初始化塊以靜態變量對待)

  • 示例1:
public class Test { 
    public static void main(String [] args){
        new Child();
    }
}
class Parent{
    { 
        System.out.println("父類普通成員初始化塊"); 
    }  
    static{
        System.out.println("父類靜態成員及初始化塊");
    }
    
    public Parent(){
        System.out.println("父類構造函數"); 
    }
}
class Child extends Parent{
    { 
        System.out.println("子類普通成員初始化塊"); 
    }  
    static{
        System.out.println("子類靜態成員及初始化塊");
    }
    
    public Child(){
        super();
        System.out.println("子類構造函數"); 
    }
}
父類靜態成員及初始化塊
子類靜態成員及初始化塊
父類普通成員初始化塊
父類構造函數
子類普通成員初始化塊
子類構造函數
  • 示例2:(證明 父構造>子普通成員初始化>子構造)
public class Test { 
    public static void main(String [] args){
        new Child();
    }
}
class Parent{
    public Parent(){
        System.out.println("父類構造函數"); 
        System.out.println("子類成員變量 height:" + ((Child)this).height);
    }
}
class Child extends Parent{
    public int height= 20;
    { 
        System.out.println("子類非靜態成員初始化塊"); 
        System.out.println("子類成員變量 height:" + this.height);
    }  
    
    public Child(){
        super();
        System.out.println("子類構造函數"); 
    }
}
父類構造函數
子類成員變量 height:0
子類非靜態成員初始化塊
子類成員變量 height:20
子類構造函數

三. 對象創建過程的說明

  1. 靜態域的初始化是在類的初始化期間,非靜態域的初始化時在類的實例創建期間。這意味這靜態域初始化在非靜態域之前。
  • 非靜態域通過構造器初始化,子類在做任何初始化之前構造器會隱含地調用父類的構造器,這保證了父類非靜態實例變量初始化早于子類。
  • 調用Class的類成員變量時,構造函數和成員變量不會執行
public class Test { 
    public static void main(String [] args){
        System.out.println(Parent.a); 
    }
}
class Parent{
    public static int a = 10;
    {
        System.out.println("父類普通成員初始化塊");
    }
    public Parent(){
        System.out.println("父類構造函數"); 
    }
}
輸出:10
  • 在類的內部,變量定義的先后順序決定了初始化的順序;
    即使變量定義散布于方法定義之間,它們仍會在任何方法(包括構造器)被調用之前得到初始化。
public class Test { 
    public static void main(String [] args){
        new Parent(); 
    }
}
class Parent{
    {
        System.out.println("普通成員初始化塊1");
    }
    public Parent(){
        System.out.println("構造函數"); 
    }
    {
        System.out.println("普通成員初始化塊2");
    }
}
普通成員初始化塊1
普通成員初始化塊2
構造函數
  • 多態情況下的對象初始化
public class Test {    
  public static void main(String [] args){
      Parent parent = new Child();
  }
}
class Parent{
  { 
      System.out.println("父類普通成員初始化塊"); 
  }  
  static{
      System.out.println("父類靜態成員及初始化塊");
  }

  public Parent(){
      System.out.println("父類構造函數"); 
  }
}
class Child extends Parent{
  { 
      System.out.println("子類普通成員初始化塊"); 
  }  
  static{
      System.out.println("子類靜態成員及初始化塊");
  }

  public Child(){
      super();
      System.out.println("子類構造函數"); 
  }
}
父類靜態成員及初始化塊
子類靜態成員及初始化塊
父類普通成員初始化塊
父類構造函數
子類普通成員初始化塊
子類構造函數

可見多態情況下的初始化與示例1中繼承情況下的初始化是一樣的,因為都是創建的子類的實例。

  • 程序的執行過程,即Java虛擬機執行Test 的靜態方法main,這也會引起Test 類的初始化。(理解面向對象而非面向過程)
public class Test {    
    public static void main(String [] args){
      System.out.println("Test- main");
    }
  
    static{
      System.out.println("靜態成員及初始化塊");
    }
}
靜態成員及初始化塊
Test- main

2015/8

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,981評論 19 139
  • 小編費力收集:給你想要的面試集合 1.C++或Java中的異常處理機制的簡單原理和應用。 當JAVA程序違反了JA...
    八爺君閱讀 4,673評論 1 114
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,142評論 0 62
  • 寒雨連江夜入吳,平明送客楚山孤。洛陽親友如相問,一片冰心在玉壺。 唐代詩人王昌齡的一首《芙蓉樓送辛漸》,將“冰心”...
    玉天下閱讀 441評論 0 1