程序中代碼執行的順序非常重要,稍有不慎便會是程序運行出錯,那么我將結合實例來分析代碼中的執行。
名詞解釋
首先了解幾個名詞:
非靜態代碼塊
直接由 { } 包起來的代碼,稱為非靜態代碼塊
靜態代碼塊
直接由 static { } 包起來的代碼,稱為靜態代碼塊
形參
比如你定義一個函數void add(int a, int b),這里的a和b就是形參。
當你進行函數調用的時候,add(1, 2),這里的1和2就是實參。
向前引用
所謂向前引用,就是在定義類、接口、方法、變量之前使用它們。
成員變量
在類體里面定義的變量稱為成員變量;
如果該成員變量有 static 關鍵字修飾,則該成員變量稱為 靜態變量 或 類變量;
如果該成員變量沒有 static 關鍵字修飾,則該成員變量被稱為 非靜態變量 或 實例變量。
局部變量
形參、方法內定義的變量、代碼塊中定義的變量,都屬于局部變量。
類變量 (靜態變量)
可以向前引用
變量屬于類本身
類變量不依賴類的實例,類變量只在初始化時候在方法區中被分配一次空間,無論類的實例被創建幾次,都不再為類變量分配空間
通過類的任意一個實例來訪問類變量,底層都將將其轉為通過類本身來訪問類變量,它們的效果是一樣的
一旦類變量的值被改變,通過類或類的任意一個實例來訪問類變量,得到的都將是被改變后的值
將在類的初始化之前初始化
實例變量(非靜態變量)
不能向前引用,如果向前引用,則稱為非法向前引用,這是不允許的
變量屬于類的實例對象
隨著類的實例被創建而分配內存空間
實例演示
public class Parent {
public int parentNum=0;
public static int staticParentNum=0;
{
System.out.println("Parent---執行非靜態代碼塊了1!");
}
{
System.out.println("Parent---執行非靜態代碼塊了2!");
}
static{
System.out.println("Parent---執行靜態代碼塊了1!");
}
static{
System.out.println("Parent---執行靜態代碼塊了2!");
}
public Parent(){
System.out.println("Parent---無參構造函數!");
}
public Parent(int parentNum){
this.parentNum=parentNum;
System.out.println("Parent---有參構造函數!");
}
public void ParentMethod(int parentNum){
this.parentNum=parentNum;
System.out.println("Parent---非靜態方法/parentNum="+parentNum);
}
public static void staticParentMethod(int staticParentNum){
Parent.staticParentNum=staticParentNum;
System.out.println("Parent---靜態方法/staticParentNum="+staticParentNum);
}
}
public class Child extends Parent{
public int childNum=0;
public static int staticChildNum=0;
{
System.out.println("Child---執行非靜態代碼塊了1!");
}
{
System.out.println("Child---執行非靜態代碼塊了2!");
}
static{
System.out.println("Child---執行靜態代碼塊了1!");
}
static{
System.out.println("Child---執行靜態代碼塊了2!");
}
public Child(){
super();
System.out.println("Child---無參構造函數!");
}
public Child(int childNum){
super(childNum);
System.out.println("Child---有參構造函數!");
}
public void childMethod(int childNum){
this.childNum=childNum;
System.out.println("Child--非靜態方法/childNum="+childNum);
}
public static void staticChildMethod(int staticChildNum){
Child.staticChildNum=staticChildNum;
System.out.println("Child---靜態方法/staticChildNum="+staticChildNum);
}
}
package test;
public class Test {
// static{
// System.out.println("Test---靜態代碼塊!");
// }
public static void main(String[] args) {
int key=10;
switch (key) {
case 0:
Parent parent=new Parent();
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Parent---執行非靜態代碼塊了1!
// Parent---執行非靜態代碼塊了2!
// Parent---無參構造函數!
// 說明:先加載靜態代碼塊,后加載非靜態代碼塊
case 1:
Child b= new Child();
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Child---執行靜態代碼塊了1!
// Child---執行靜態代碼塊了2!
// Parent---執行非靜態代碼塊了1!
// Parent---執行非靜態代碼塊了2!
// Parent---無參構造函數!
// Child---執行非靜態代碼塊了1!
// Child---執行非靜態代碼塊了2!
// Child---無參構造函數!
// 說明:創建子類,會先執行父類,先執行父類靜態——>子類靜態——>父類非靜態——>父類構造
//——>子類非靜態——>子類構造
case 2:
Child c= new Child(4);
//這個構造函數中指明了調用父類的有參構造函數,若不指定,則調用父類無參構造函數
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Child---執行靜態代碼塊了1!
// Child---執行靜態代碼塊了2!
// Parent---執行非靜態代碼塊了1!
// Parent---執行非靜態代碼塊了2!
// Parent---有參構造函數!
// Child---執行非靜態代碼塊了1!
// Child---執行非靜態代碼塊了2!
// Child---有參構造函數!
說明:靜態代碼塊或非靜態代碼塊執行順序,按照代碼前后編寫順序。
case 3:
Child d= new Child();
Child e= new Child(4);
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Child---執行靜態代碼塊了1!
// Child---執行靜態代碼塊了2!
// Parent---執行非靜態代碼塊了1!
// Parent---執行非靜態代碼塊了2!
// Parent---無參構造函數!
// Child---執行非靜態代碼塊了1!
// Child---執行非靜態代碼塊了2!
// Child---無參構造函數!
// Parent---執行非靜態代碼塊了1!
// Parent---執行非靜態代碼塊了2!
// Parent---有參構造函數!
// Child---執行非靜態代碼塊了1!
// Child---執行非靜態代碼塊了2!
// Child---有參構造函數!
說明:創建多個子類,但父類靜態代碼塊只執行一次。
case 4:
Child.staticChildMethod(4);
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Child---執行靜態代碼塊了1!
// Child---執行靜態代碼塊了2!
// Child---靜態方法/staticChildNum=4
說明:靜態方法只可以調用靜態變量。
case 5:
Parent.staticParentMethod(5);
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Parent---靜態方法/staticParentNum=5
說明:靜態方法可通過 父類名.靜態方法() 調用。
case 6:
System.out.println("父類的靜態變量值staticParentNum="+Parent.staticParentNum);
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// 父類的靜態變量值staticParentNum=0
說明:調用靜態變量時,靜態代碼塊會執行。
case 7:
System.out.println("子類的靜態變量值staticChildNum="+Child.staticChildNum);
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Child---執行靜態代碼塊了1!
// Child---執行靜態代碼塊了2!
// 子類的靜態變量值staticChildNum=0
說明:調用子類靜態變量,父類靜態代碼塊和子類靜態代碼塊會被執行。
case 8:
System.out.println("父類的靜態變量值staticParentNum="+Parent.staticParentNum);
System.out.println("子類的靜態變量值staticChildNum="+Child.staticChildNum);
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// 父類的靜態變量值staticParentNum=0
// Child---執行靜態代碼塊了1!
// Child---執行靜態代碼塊了2!
// 子類的靜態變量值staticChildNum=0
case 9:
Child f= new Child();
f.ParentMethod(3);
break;
// Parent---執行靜態代碼塊了1!
// Parent---執行靜態代碼塊了2!
// Child---執行靜態代碼塊了1!
// Child---執行靜態代碼塊了2!
// Parent---執行非靜態代碼塊了1!
// Parent---執行非靜態代碼塊了2!
// Parent---無參構造函數!
// Child---執行非靜態代碼塊了1!
// Child---執行非靜態代碼塊了2!
// Child---無參構造函數!
// Parent---非靜態方法/parentNum=3
說明:創建子類,用子類調用父類方法,非靜態方法可以調用靜態變量。
default:
break;
}
}
}
總結
Java代碼初始化順序
由 static 關鍵字修飾的(如:類變量(靜態變量)、靜態代碼塊)將在類被初始化創建實例對象之前被初始化,而且是按順序從上到下依次被執行。靜態(類變量、靜態代碼塊)屬于類本身,不依賴于類的實例。
沒有 static 關鍵字修飾的(如:實例變量(非靜態變量)、非靜態代碼塊)初始化實際上是會被提取到類的構造器中被執行的,但是會比類構造器中的代碼塊優先執行到,非靜態(實例變量、非靜態代碼塊)的地位是相等的,它們將按順序被執行。
類變量(靜態變量)、實例變量(非靜態變量)、靜態代碼塊、非靜態代碼塊的初始化時機
由 static 關鍵字修飾的(如:類變量[靜態變量]、靜態代碼塊)將在類被初始化創建實例對象之前被初始化,而且是按順序從上到下依次被執行;
沒有 static 關鍵字修飾的(如:實例變量[非靜態變量]、非靜態代碼塊)初始化實際上是會被提取到類的構造器中被執行的,但是會比類構造器中的 代碼塊優先執行到,其也是按順序從上到下依次被執行。
容易混淆的一個知識點
靜態方法只允許直接訪問靜態成員,而實例方法中可以訪問靜態成員和實例成員,原因是類還沒有實例化,所實例成員也沒有被創建,靜態方法中因此也不能用this。
歡迎關注公眾號交流!