1. 程式人生 > 程式設計 >Java程式碼塊與程式碼載入順序原理詳解

Java程式碼塊與程式碼載入順序原理詳解

這篇文章主要介紹了Java程式碼塊與程式碼載入順序原理詳解,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

本文首先介紹幾個基本的名次,然後介紹了三種程式碼塊的特性和使用方法。

在面試大型公司時,如果遇到大型國企或者大的網際網路私企,筆試中經常遇到程式碼塊和程式碼載入順序的筆試題。這裡做一個總結,也方便各位小夥伴飆車不會飄。

名詞解釋

程式碼塊

由 { } 包起來的程式碼,稱為程式碼塊

靜態程式碼塊

由 static { } 包起來的程式碼,稱為靜態程式碼塊。

不同型別變數定義示例:

class Demo{
  String x;// 非靜態成員變數,又稱為屬性,對該類不同的物件來說,屬性互不相同
  static int y = 32;// 類變數,一個類中只有一個該變數,該類不同的物件共享同一個靜態成員變數
  public static void main(String[] args){
    int z = 0;// 區域性變數,只在方法內部可見,在方法結束後由垃圾收集器自動回收
  }
}

區域性程式碼塊

位置:區域性位置(方法內部)。

作用:限定變數的生命週期,儘早釋放,節約記憶體。

呼叫:呼叫其所在的方法時執行。

方法中的區域性程式碼塊一般進行一次性地呼叫,呼叫完立刻釋放空間,避免在接下來的呼叫過程中佔用棧空間。棧空間記憶體有限,方法呼叫可能會生成很多區域性變數導致棧記憶體不足,使用區域性程式碼塊可以避免此缺陷。

public class 區域性程式碼塊 {

   public static void go() {
    // 區域性程式碼塊
    {
      int age = 30;
      System.out.print("go: " + age);
    }
  }
  public static void main(String[] args) {
    go();
  }
}

構造程式碼塊

位置:類成員的位置,即類中方法之外的位置。

作用:把多個構造方法共同的部分提取出來,共用構造程式碼塊。

呼叫:每次呼叫構造方法時,都會優先於構造方法執行,也就是每次new一個物件時自動呼叫,實現物件初始化。

public class A {
  int i = 1;
  int initValue;//成員變數,初始化交給程式碼塊來完成
  A(){
    System.out.println("構造方法在程式碼塊執行後執行");
  }
  {
    System.out.println("程式碼塊從上至下依次執行");
    //程式碼塊的作用體現於此:在呼叫構造方法之前,用某段程式碼對成員變數進行初始化。
    //而不是在構造方法呼叫時再進行。
    for (int i = 0;i < 100;i ++) {
      initValue += i;
    }
  }
  {
    System.out.println(initValue);
    System.out.println(i);//此時會列印1
    int i = 2;//區域性變數,和成員變數不衝突,但會優先使用程式碼塊的變數
    System.out.println(i);//此時列印2
    //System.out.println(j);//提示非法向後引用,因為此時j的的初始化還沒開始。
  }
  int j = 2;
  {
    System.out.println(j);
    System.out.println(i);//程式碼塊中的變數執行後自動釋放,不會影響程式碼塊之外的程式碼
  }

}
public class 構造程式碼塊 {
  @Test
  public void test() {
    A a = new A();
  }
}

執行結果

程式碼塊從上至下依次執行
1
2
構造方法在程式碼塊執行後執行

靜態程式碼塊

位置:類成員位置。

作用:對類進行一些初始化,只加載一次。當new多個物件時,只有第一次會呼叫靜態程式碼塊,因為靜態程式碼塊和類變數一樣,

是屬於類的,所有物件共享一份。

呼叫: new 一個物件時自動呼叫。

public class 靜態程式碼塊 {
@Test
public void test() {
  C c1 = new C();
  C c2 = new C();
  //結果,靜態程式碼塊只會呼叫一次,類的所有物件共享該程式碼塊
System.out.println("我是普通方法");
}
}
class C{
  C(){
    System.out.println("構造方法呼叫");
  }
  {
    System.out.println("程式碼塊呼叫");
  }
  static {
    System.out.println("靜態程式碼塊呼叫");
  }
}

呼叫結果:

靜態程式碼塊呼叫
程式碼塊呼叫
構造方法呼叫
程式碼塊呼叫
構造方法呼叫
我是普通方法

執行順序 靜態程式碼塊 —–> 構造程式碼塊 ——-> 構造方法

筆試題

寫出下列程式輸出結果:

public class HelloA {
  public HelloA(){
    System.out.println("HelloA");
  }
  {
    System.out.println("I'm A class");
  }
  static {
    System.out.println("static A");
  }
}
public class HelloB extends HelloA {
  public HelloB(){
    System.out.println("HelloB");
  }
  {
    System.out.println("I'm B class");
  }
  static {
    System.out.println("static B");
  }
  public static void main(String[] args) {
    new HelloB();
  }
}

執行結果:

分析:首先要知道靜態程式碼塊是隨著類的載入而載入,而構造程式碼塊和構造方法都是隨著物件的建立而載入。

1,在編譯HelloB.java時,由於HelloB 繼承 HelloA,先載入了HelloA類,因此HelloA類的靜態程式碼塊首先執行,而後載入HelloB類,HelloB類的靜態程式碼塊執行,這沒什麼好說的。

2,然後建立HelloB的物件,大家都知道構造程式碼塊優先於構造方法執行,這時候問題來了,這時應該先看HelloB類的構造方法,HelloB類裡的構造方法裡有一句隱式的super()首先被執行,所以找到HelloA類的構造方法,而HelloA類的構造方法中也有一句隱式的super()執行(呼叫Object類的構造方法),並沒有什麼返回結果,接下來才是在執行HelloA類構造方法的方法體前先執行了HelloA類的構造程式碼塊(I'm A class),再執行HelloA類構造方法的方法體(也就是Hello A),最後又回到HelloB類的構造方法中,這時HelloB類的super()已經執行完了,在執行HelloB類構造方法的方法體前先執行HelloB類的構造程式碼塊(I'm B class),再執行子類構造方法的方法體(HellB)。

無繼承初始化順序:

有繼承初始化順序:

接下來看一道阿里筆試題:

public class B{
  public static B t1 = new B();
  public static B t2 = new B();
  {
    System.out.println("構造塊");
  }
  static {
    System.out.println("靜態塊");
  }
  public static void main(String[] args) {
    B t =new B();
  }
}

執行結果:

總結

Java程式碼初始化順序

由 static 關鍵字修飾的,如類變數和靜態程式碼塊,將在類建立例項之前被初始化,而且是按順序從上到下依次被執行。(類變數、靜態程式碼塊)屬於類本身,不依賴於類的例項。

沒有 static 關鍵字修飾的(如:例項變數(非靜態變數)、非靜態程式碼塊)初始化實際上是會被提取到類的構造器中被執行的,但是會比類構造器中的程式碼塊優先執行。例項變數、非靜態程式碼塊的地位是相等的,它們將按順序被執行。
容易混淆的一個知識點

靜態方法只允許直接訪問靜態成員,而例項方法中可以訪問靜態成員和例項成員,原因是類還沒有例項化,所以例項成員也沒有被建立,靜態方法中因此也不能用this。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。