一、先來了解幾個概念
1、靜態代碼塊
(1)格式
在java類中(方法中不能存在靜態代碼塊)使用static關鍵字和{}聲明的代碼塊
public class CodeBlock { static{ System.out.println("靜態代碼塊"); } }
(2)執行時機
靜態代碼塊在類被加載的時候就運行了,而且只運行一次,並且優先於各種代碼塊以及構造函數。如果一個類中有多個靜態代碼塊,會按照書寫順序依次執行。后面在比較的時候會通過具體實例來證明。
(3)靜態代碼塊的作用
一般情況下,如果有些代碼需要在項目啟動的時候就執行,這時候就需要靜態代碼塊。比如一個項目啟動需要加載的很多配置文件等資源,我們就可以都放入靜態代碼塊中。
(4)靜態代碼塊不能存在任何方法體中
首先我們要明確靜態代碼塊是在類加載的時候就要運行了。我們分情況討論:
- 對於普通方法:由於普通方法是通過加載類,然后new出實例化對象,通過對象才能運行這個方法,而靜態代碼塊只需要加載類之后就能運行了。
- 對於靜態方法:在類加載的時候,靜態方法也已經加載了,但是我們必須要通過類名或者對象名才能訪問,也就是說相比於靜態代碼塊,靜態代碼塊是主動運行的,而靜態方法是被動運行的。
(5)靜態代碼塊不能訪問普通變量
普通變量只能通過對象來調用,是不能放在靜態代碼塊中的。
2、構造代碼塊
(1)格式
在java類中使用{}聲明的代碼塊(和靜態代碼塊的區別是少了static關鍵字):
public class CodeBlock { static{ System.out.println("靜態代碼塊"); } { System.out.println("構造代碼塊"); } }
(2)執行時機
構造代碼塊在創建對象時被調用,每次創建對象都會調用一次,但是優先於構造函數執行。
但是要明白:構造代碼塊依托於構造函數,也就是說,如果你不實例化對象,構造代碼塊是不會執行的
public class Constructor { { System.out.println("構造代碼塊"); } public Constructor(){ System.out.println("無參構造函數"); } public Constructor(String str){ System.out.println("有參構造函數"); } public static void main(String[] args) { new Constructor(); System.out.println(); new Constructor("構造代碼塊"); } }
(3)構造代碼塊的作用
和構造函數的作用類似,都能對對象進行初始化,並且只要創建一個對象,構造代碼塊都會執行一次。
反過來,構造函數則不一定每個對象建立時都執行(多個構造函數情況下,建立對象時,傳入的參數不同則初始化使用對應的構造函數)。
3、構造函數
(1)構造函數的命名必須和類名完全相同。在java中普通函數可以和構造函數同名,但是必須帶有返回值;
(2)構造函數的功能主要用於在類的對象創建時定義初始化的狀態。它沒有返回值,也不能用void來修飾。這就保證了它不僅什么也不用自動返回,而且根本不能有任何選擇。而其他方法都有返回值,即使是void返回值。
(3)構造函數不能被直接調用,必須通過new運算符在創建對象時才會自動調用;而一般的方法是在程序執行到它的時候被調用的;
(4)默認先調用父類的無參構造函數
4、普通代碼塊
普通代碼塊和構造代碼塊的區別是:
- 構造代碼塊是在類中定義的,
- 普通代碼塊是在方法體中定義的。且普通代碼塊的執行順序和書寫順序一致。
public void sayHello(){ { System.out.println("普通代碼塊"); } }
5、各種類型變量的默認初始值
JVM 類加載機制中提到,類連接 (驗證, 准備, 解析)中准備工作:
負責為類的類變量(非對象變量)分配內存,並設置默認初始值,准備類中每個字段、方法和實現接口所需的數據結構
這里說的初始值都是默認的值, 並不是程序中指定的值 :看例子
public class Text { public static int k = 10; public int a = print("a"); public static int b = print("b"); public static Text t1 = new Text("t1"); public static Text t2 = new Text("t2"); public static int i = print("i"); public static int n = 99; public int j = print("j"); }
經過准備工作后,類中變量的初始值為如下:
k =0; b=0; t1=null; t2=null; i=0; n=0;
二、正題:Java的實例化順序
1、牢記:靜態和非靜態分開處理
(1)使用到靜態加載時,靜態又分為: 靜態變量, 靜態代碼塊,其中加載順序是按照類中書寫的先后順序加載的
(2)非靜態加載順序: 按照非靜態書寫順序加載 /()執行
(3)靜態方法,實例方法只有在調用的時候才會去執行
(4)當靜態加載中遇到需要加載非靜態的情況: 先加載非靜態再加載靜態(因為非靜態可以訪問靜態,而靜態不能訪問非靜態)
public static Text t1 = new Text("t1"); // 當加載靜態變量是需要先加載構造器, 那就轉為先加載所有非靜態屬性
2、靜態變量聲明 一定 放在使用前面
3、main是否第一句先執行
Java程序運行時,第一件事情就是試圖訪問main方法,因為main相等於程序的入口,如果沒有main方法,程序將無法啟動,main方法更是占一個獨立的線程,找到main方法后,是不是就會執行mian方法塊里的第一句話呢?
答:不是
因為main方法雖然是一個特殊的靜態方法,但是還是靜態方法,此時JVM會加載main方法所在的類,試圖找到類中其他靜態部分,即首先會找main方法所在的類。
public class JVMTest { static{ System.out.println("Main 方法所在靜態代碼塊 static1"); } public static void main(String[] args) { System.out.println("main start"); A a = new A(); System.out.println(A.width); System.out.println(a.width); } static{ System.out.println("Main 方法所在靜態代碼塊 static2"); } } class A{ public static int width = 100; static{ System.out.println("靜態初始化類A"); width = 30; } public A(){ System.out.println("創建A類的對象"); } }
4、父類、子類加載順序
/* * 父類 */ public class JVMParent { public static int width = 100; public static int count; { System.out.println("parent no static code block :" + count); } static{ System.out.println("parent static's count:" + count); } JVMParent(int a){ System.out.println("parent init one parameter"); } JVMParent(){ System.out.println("parent init"); } }
/* * 子類 */
public class JVMSons extends JVMParent { { System.out.println("son no static code block :" + count); } static { System.out.println("son static 1"); } public static int count1; JVMSons() { System.out.println("son init:" + count); } static { System.out.println("son static 2"); } public static void main(String[] args) { System.out.println("son main start"); JVMSons a = new JVMSons(); } }
先自己分析一下,然后看總結。
5、總結
1、父類的靜態變量和靜態塊賦值(按照聲明順序)
2、自身的靜態變量和靜態塊賦值(按照聲明順序)
3、main方法
3、父類的成員變量和塊賦值(按照聲明順序)
4、父類構造器賦值
5、自身成員變量和塊賦值(按照聲明順序)
6、自身構造器賦值
7、靜態方法,實例方法只有在調用的時候才會去執行
三、參考
https://www.cnblogs.com/zhongHW/p/11047007.html
https://www.cnblogs.com/UncleWang001/articles/10429801.html