先看一道Java面試題:java
1 public class Baset { 2 private String baseName = "base"; 3 // 構造方法 4 public Baset() { 5 callName(); 6 } 7 // 成員方法 8 public void callName() { 9 // TODO Auto-generated method stub 10 System.out.println("basename:" + baseName); 11 } 12 //靜態內部類 13 static class Sub extends Baset {//static必須寫在開頭 14 // 靜態字段 15 private String baseName = "sub"; 16 public Sub() { 17 callName(); 18 } 19 // 重寫父類的方法 20 public void callName() { 21 System.out.println("subname:" + baseName); 22 } 23 } 24 25 public static void main(String[] args) { 26 Baset base = new Sub(); 27 } 28 }
求這段程序的輸出。面試
解答此題關鍵在於理解和掌握類的加載過程以及子類繼承父類後,重寫方法的調用問題:函數
1、從程序的執行順序去解答:spa
1.編譯;當這個類被編譯通知後,會在相應的目錄下生成兩個.class 文件。一個是 Base.class,另一個就是Base$Sub.class。這個時候類加載器將這兩個.class 文件加載到內存code
二、Base base= new Sub():blog
聲明父類變量base對子類的引用,JAVA類加載器將Base,Sub類加載到JVM(Java虛擬機);繼承
三、JVM爲Base,Sub 的的成員開闢內存空間內存
此時,Base 和Sub類中的值爲null;虛擬機
四、new Sub();it
這個時候會調用Sub類的隱式構造方法,
Sub的構造方法本質爲:
public Sub(){
super();// 調用父類的構造方法。必須在構造方法中的第一行,爲何呢?這是由於在一些程序的升級中,要兼容舊版本的一些功能,父類即原先的一些初始化信息也要保證 //被執行到,而後執行當前
baseName = "sub";//子類字段初始化
}
new Sub()執行到super()這行代碼也就是跑到父類中去執行了,咱們跳轉到父類中的無參構造方法中執行,最後執行Sub()中的baseName = "sub"
五、public Base() ;
父類無參構造方法的本質爲:
public Base(){
baseName= "base";//父類字段初始化
callName();
}
即將父類的baseName賦值爲「base」,賦值後調用callName();
六、callName()方法在子類中被重寫,所以調用子類的callName(),子類的callName方法執行,打印輸出的是子類的baseName 字段的值,而這個時候子類的構造函數中字段的賦值還未執行。
七、父類的構造函數執行完畢,這個時候又回到子類當中,從super()的下一行繼續執行,這個時候才爲子類字段baseName 分配好存儲空間,隨後爲其賦值:
可見,在baseName = "sub"執行前,子類的callName()已經執行,因此子類的baseName爲默認值狀態null;
二、另寫兩個程序,進行更具體的分析
一、第一個程序:
1 public class InitialOrderTest { 2 // 變量 3 public String field = "變量"; 4 // 靜態變量 5 public static String staticField="靜態變量"; 6 //父類靜態方法 7 public static void Order(){ 8 System.out.print("父類靜態方法"); 9 System.out.println("staticField:"+staticField); 10 } 11 12 // 靜態初始代碼塊 13 static{ 14 System.out.println("靜態初始化塊"); 15 System.out.println("staticField:"+staticField); 16 } 17 // 初始化代碼塊 18 { 19 System.out.println("初始化代碼塊"); 20 System.out.println("field:"+field); 21 } 22 // 構造函數 23 public InitialOrderTest(){ 24 System.out.println("構造器"); 25 26 } 27 28 public static void main(String[] args) { 29 System.out.println("-----[[-------"); 30 System.out.println(InitialOrderTest.staticField); 31 InitialOrderTest.Order(); 32 System.out.println("------]]------"); 33 InitialOrderTest i = new InitialOrderTest(); 34 System.out.println("-----[[-------"); 35 System.out.println(InitialOrderTest.staticField); 36 InitialOrderTest.Order(); 37 System.out.println("------]]------"); 38 39 } 40 }
執行結果爲:
第一個程序總結:
1)、java中的塊分爲靜態塊(static{})和非靜態塊({}),這兩種的執行是有區別的:
非靜態塊的執行時間是:在執行構造函數以前。 靜態塊的執行時間是:class文件加載時執行。
static類型的屬性也是在類加載時執行的。
2)、可見Java類的實例變量初始化的過程:
static類型的成員屬性執行,靜態塊(static{})按順序執行,而後非靜態成員變量初始化,非靜態代碼塊({})執行,最後執行構造方法。
static類型與static塊按前後順序執行。
---------------------------------------------------------------------------------------------------------------------------------------------------
二、第二個程序:
1 public class BaseTest { 2 // 父類變量 3 private String baseName = "base"; 4 // 父類靜態變量 5 private static String staticField = "父類靜態變量"; 6 // 父類靜態方法 7 public static void Order() { 8 System.out.println("父類靜態方法-"); 9 System.out.println("staticField:" + staticField); 10 } 11 // 父類靜態初始代碼塊 12 static { 13 System.out.println("父類靜態初始化代碼塊-"); 14 System.out.println("staticField:" + staticField); 15 } 16 // 初始化代碼塊 17 { 18 System.out.println("父類非靜態初始化代碼塊-"); 19 System.out.println("baseName:" + baseName); 20 } 21 // 構造函數 22 public BaseTest() { 23 System.out.println("父類構造方法"); 24 callName(); 25 } 26 // 成員方法 27 public void callName() { 28 System.out.println("父類callName方法-"); 29 System.out.println("baseName:" + baseName); 30 } 31 32 // 靜態內部類 33 static class Sub extends BaseTest { 34 // 子類變量 35 private String baseName = "sub"; 36 // 子類 靜態變量 37 private static String staticField = "子類靜態變量"; 38 39 // 子類靜態方法 40 public static void Order() { 41 System.out.println("子類靜態方法-"); 42 System.out.println("staticField:" + staticField); 43 } 44 45 // 子類靜態初始化代碼塊 46 static { 47 System.out.println("子類靜態初始化代碼塊-"); 48 System.out.println("staticField:" + staticField); 49 } 50 // 子類非靜態初始化代碼塊 51 { 52 System.out.println("子類非靜態初始化代碼塊-"); 53 System.out.println("baseName:" + baseName); 54 } 55 56 public Sub() { 57 System.out.println("子類構造方法"); 58 callName(); 59 } 60 61 public void callName() { 62 System.out.println("子類重寫父類callName方法-"); 63 System.out.println("baseName:" + baseName); 64 } 65 } 66 67 public static void main(String[] args) { 68 69 BaseTest b = new Sub(); 70 71 System.out.println("-----[[-------"); 72 Sub.Order(); 73 System.out.println(Sub.staticField); 74 System.out.println(BaseTest.staticField); 75 BaseTest.Order(); 76 System.out.println("------]]------"); 77 78 } 79 }
執行結果:
第二個程序總結:
1)、可見Java初始化的順序:
1 1 父類靜態代碼塊-static{ } 2 2 父類靜態變量初始化 3 3 子類靜態代碼塊-static{ } 4 4 子類靜態變量初始化 5 5 父類非靜態代碼塊-{} 6 6 父類非靜態變量初始化 7 7 父類構造方法 8 8 子類非靜態代碼塊-{} 9 9 子類非靜態變量初始化 10 10 子類構造方法