Java類中各部分的執行順序
直接看程式碼,這個例子寫的比較複雜,包含了大多數情況,所以讀起來得耐心點。
public class ClassLoadTest {
ClassLoadTest(){
System.out.println("構造一個ClassLoaderTest");
}
{
System.out.println("CLassLoader的非靜態程式碼塊");
}
static {
System.out.println("CLassLoader的靜態程式碼塊");
}
public static void main(String[] args){
A a = new A("a");
System.out.println("構造一個子類物件,並賦值給子類引用");
C c = new C();
System.out.println("構造一個子類物件,並賦值給父類引用");
B b = new C();
System.out.println("構造一個父類物件,並賦值給父類引用");
B b1 = new B();
}
}
class A {
A(String s){
System.out.println("A 構造方法-----開始" );
B b1 = new B("構造方法中生成的B");
System.out.println("A 的構造方法"+s);
funA2();
System.out.println("A 構造方法--------------完畢");
}
{
System.out.println("A 的非靜態程式碼塊-----開始");
funA2();
funA1();
System.out.println("A 的非靜態程式碼塊-------------完畢" );
}
B b1 = new B("非靜態全域性變數生成的B");
static B b2 = new B("靜態全域性變數生成的B");
static {
System.out.println("A的靜態程式碼塊-----開始");
B b1 = new B("靜態程式碼塊");
System.out.println("A 的靜態程式碼塊--------------完畢");
}
public static void funA1(){
System.out.println("A 的靜態方法");
}
public void funA2(){
System.out.println("A 的成員方法");
}
}
class B{
String s = "b";
static {
System.out.println("B 的靜態程式碼塊");
}
{
System.out.println("B 的非靜態程式碼塊");
}
B(){
System.out.println("B 的無參構造方法");
}
B(String s){
System.out.println("構造了一個 B--"+s);
}
public static void funA1(){
System.out.println("B 的靜態方法");
}
public void funA2(){
System.out.println("B 的成員方法");
}
private void fun3(){
System.out.println("B 的private成員方法");
}
}
class C extends B{
String s = "c";
static {
System.out.println("C 的靜態程式碼塊");
}
{
System.out.println("C 的非靜態程式碼塊");
}
C(){
System.out.println("C的無參構造方法");
}
private static void fun3(){
}
@Override
public void funA2(){
funA1();
System.out.println("override");
}
}
CLassLoader的靜態程式碼塊
B 的靜態程式碼塊
B 的非靜態程式碼塊
構造了一個 B–靜態全域性變數生成的B
A的靜態程式碼塊—–開始
B 的非靜態程式碼塊
構造了一個 B–靜態程式碼塊
A 的靜態程式碼塊————–完畢
A 的非靜態程式碼塊—–開始
A 的成員方法
A 的靜態方法
A 的非靜態程式碼塊————-完畢
B 的非靜態程式碼塊
構造了一個 B–非靜態全域性變數生成的B
A 構造方法—–開始
B 的非靜態程式碼塊
構造了一個 B–構造方法中生成的B
A 的構造方法a
A 的成員方法
A 構造方法————–完畢
構造一個子類物件,並賦值給子類引用
C 的靜態程式碼塊
B 的非靜態程式碼塊
B 的無參構造方法
C 的非靜態程式碼塊
C的無參構造方法
構造一個子類物件,並賦值給父類引用
B 的非靜態程式碼塊
B 的無參構造方法
C 的非靜態程式碼塊
C的無參構造方法
構造一個父類物件,並賦值給父類引用
B 的非靜態程式碼塊
B 的無參構造方法
類的初始化原則:
- 靜態的東西最先執行。靜態全域性變數和靜態程式碼塊誰先執行取決於誰寫在前面。且靜態的東西永遠只執行一次。第二次new的時候既不會初始化static變數,也不會執行static程式碼塊。
- 對於非靜態的東西,先初始化變數,最後執行構造方法,與程式碼寫的順序無關。
- 構造子類時,首先會構造一個父類。等父類構造完成後才會構造子類。非靜態程式碼塊和物件繫結,所以父類的非靜態程式碼塊是在構造父類物件的時候執行的。
- 另外可以看到,執行Main方法並不需要生成物件,直接用類呼叫就行了,但是會呼叫static程式碼塊。如果有static物件的話也會先生成,最後呼叫static的Main方法。
幾個問題:
- 構造方法中能不能呼叫成員函式?可以,但實際使用中要注意空指標,因為成員方法用到的變數不一定在構造物件的時候就已經初始化了。例如,在Android中就不能在Application的構造方法中呼叫getSharedPreferences()。
- 非靜態程式碼塊,成員變數,構造方法的呼叫順序?全域性變數和非靜態程式碼塊的執行順序看誰寫在前面,構造方法總是最後呼叫。
- 非靜態程式碼塊中能夠呼叫成員函式?可以,靜態函式也可以。
為什麼構造方法總是最後執行的原因:
構造方法的作用是對類初始化。具體來講是對類的變數進行初始化。那麼首先得有變數才能對變數進行初始化。所以總是變數先構造出引用,最後建構函式才得到執行。
補充:
今天做美麗聯合的筆試,遇到這麼一個奇怪的題:
public class Meili {
static Meili meili = new Meili();
{
System.out.println("非靜態程式碼塊");
}
static {
System.out.println("靜態程式碼塊");
}
public static void main(String[] args){
System.out.println("開始執行Main方法");
Meili meili = new Meili();
}
}
求輸出是什麼?
注意這塊程式碼和上面的例子不同之處在於,它直接用執行類(包含Main方法的類)來生成物件。結果是:
非靜態程式碼塊
靜態程式碼塊
開始執行Main方法
非靜態程式碼塊
這段程式碼的執行順序是這樣的:先生成static的Meili物件,於是執行程式碼塊,但是奇怪的是居然先執行了非靜態程式碼塊。當靜態的Meili物件生成完了之後去執行Main方法。然後在Main方法中生成另一個Meili物件,此時由於靜態程式碼塊已經執行過了,所以只會執行非靜態程式碼塊,程式結束。
這段程式碼詭異的地方在於,每個Meili物件都持有一個Meili物件,即第三行的靜態Meili引用所指的物件。如果去掉static會棧溢位,因為會無限呼叫構造方法。但是為什麼非靜態程式碼塊會在靜態程式碼之前呼叫呢?之前的測試證明了靜態程式碼塊是一定會在非靜態程式碼塊之前執行的呀?希望知道的大佬不吝賜教。