1. 程式人生 > >類載入,構造器初始化及static關鍵字

類載入,構造器初始化及static關鍵字

一.類載入與構造器初始化
1.類載入:Java命令的作用是啟動虛擬機器,虛擬機器通過輸入流,從磁碟上將位元組碼檔案(.class檔案)中的內容讀入虛擬機器,並儲存起來的過程就是類載入。

2.類載入特性: 在Java中,類是按需載入,只有當需要用到這個類的時候,才會載入這個類,並且在虛擬機器的生命週期中一個類只被載入一次。

3.類載入的原則:延遲載入,能少載入就少載入,因為虛擬機器的空間是有限的

4.從概念上講,初始化與建立是彼此獨立的,然而java中,初始化和建立是捆綁在一起的,兩者不能分離

5.對於方法內的區域性變數,聲明後不會自動初始化賦值,如果沒手動初始化,那麼使用該變數時,編譯器會報錯
對於類中的資料成員(也就是屬性),聲明後系統會自動初始化為該類資料型別的初始值

6.在生成物件的過程中,會先初始化物件的成員變數,然後再執行構造器。也就是說類中的變數會在任何方法(包括構造器)呼叫之前得到初始化,即使變數散步於方法定義之間。

7.類載入的步驟:
在Java中,類裝載器把一個類裝入Java虛擬機器中,要經過三個步驟來完成:裝載、連結和初始化,其中連結又可以分成校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工作如下:

  • 裝載:查詢和匯入類或介面的二進位制資料
  • 連結:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的
  • 校驗:檢查匯入類或介面的二進位制資料的正確性
  • 準備:給類的靜態變數分配並初始化儲存空間
  • 解析:將符號引用轉成直接引用
  • 初始化:啟用類的靜態變數的初始化Java程式碼和靜態Java程式碼塊

8.類載入的時機:
(1)第一次建立物件時要載入類(因為建立物件會呼叫構造方法,而構造方法也是static方法,只不過是隱式的)
(2)呼叫靜態方法時要載入類,訪問靜態屬性時會載入類
(3)載入子類時必定會先載入父類
(4)建立物件引用不載入類
(5)子類呼叫父類的靜態方法時

  • 當子類沒有與父類同名的靜態方法時(這時是執行父類的方法,所以只需載入父類),只加載父類,不載入子類
  • 當子類有與父類同名的靜態方法時(這是是執行子類的方法,需要載入子類,所以必須先載入父類),既載入父類,又載入子類

(6)訪問靜態常量,如果編譯器可以計算出常量的值,則不會載入類,例如:public static final int a =123;否則會載入類,例如:public static final int a = math.PI;
總的來說,類是在其任何static成員被訪問時載入的.初次使用之處也是static初始化之處,所有static物件和static程式碼段都會在載入時依程式中的順序(即定義類時的書寫順序)而依次初始化,當然定義為static的東西只會被初始化一次

9.物件的初始化順序:首先執行父類靜態的內容,父類靜態的內容執行完畢後,接著去執行子類的靜態的內容,當子類的靜態內容執行完畢之後,再去看父類有沒有非靜態程式碼塊,如果有就執行父類的非靜態程式碼塊,父類的非靜態程式碼塊執行完畢,接著執行父類的構造方法;父類的構造方法執行完畢之後,它接著去看子類有沒有非靜態程式碼塊,如果有就執行子類的非靜態程式碼塊。子類的非靜態程式碼塊執行完畢再去執行子類的構造方法。
總之一句話,靜態程式碼塊內容先執行,接著執行父類非靜態程式碼塊和構造方法,然後執行子類非靜態程式碼塊和構造方法。
注意:子類的構造方法,不管這個構造方法帶不帶引數,預設的它都會先去尋找父類的不帶引數的構造方法。如果父類沒有不帶引數的構造方法,那麼子類必須用supper關鍵子來呼叫父類帶引數的構造方法,否則編譯不能通過。

10.一個類中物件的定義一般都是分為以下兩步來進行的:
(1)A a; //定義了一個類A的引用
(2)a=new A(“10″,”2563”); //真正地建立了物件a,也就是a指向了記憶體中一塊連續的區域.

11.本地方法是一種在java中呼叫非java程式碼的方式

二.static關鍵字
static表示“全域性”或者“靜態”的意思,用來修飾成員變數和成員方法,也可以形成靜態static程式碼塊,但是Java語言中沒有全域性變數的概念。

被static修飾的成員變數和成員方法獨立於該類的任何物件。也就是說,它不依賴類特定的例項,被類的所有例項共享。只要這個類被載入,Java虛擬機器就能根據類名在執行時資料區的方法區內定找到他們。因此,static物件可以在它的任何物件建立之前訪問,無需引用任何物件。

用public修飾的static成員變數和成員方法本質是全域性變數和全域性方法,當宣告它類的物件時,不生成static變數的副本,而是類的所有例項共享同一個static變數。

static變數前可以有private修飾,表示這個變數可以在類的靜態程式碼塊中,或者類的其他靜態成員方法中使用(當然也可以在非靜態成員方法中使用),但是不能在其他類中通過類名來直接引用,這一點很重要。實際上只需要搞明白,private是訪問許可權限定,static表示不要例項化就可以使用,這樣就容易理解多了。static前面加上其它訪問許可權關鍵字的效果也以此類推。

static修飾的成員變數和成員方法習慣上稱為靜態變數和靜態方法,可以直接通過類名來訪問,訪問語法為:

  • 類名.靜態方法名(引數列表…)
  • 類名.靜態變數名

用static修飾的程式碼塊表示靜態程式碼塊,當Java虛擬機器(JVM)載入類時,就會執行該程式碼塊。

三.Static關鍵字的用途
1.Static變數
static變數也稱作靜態變數(也叫類變數),靜態變數和非靜態變數(例項變數)的區別是:靜態變數被所有的物件所共享,在記憶體中只有一個副本(因此在程式中任何物件對靜態變數做修改,其他物件看到的是修改後的值),它當且僅當在類初次載入時會被初始化。可用類名直接訪問(方便),當然也可以通過物件來訪問(但是這是不推薦的)。而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響。
static成員變數的初始化順序按照定義的順序進行初始化。
注意:不能把任何方法體內的變數宣告為靜態(即static是不允許用來修飾區域性變數)

2.Static方法
靜態方法可以直接通過類名呼叫(任何的例項也可以呼叫),因此靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的例項變數和例項方法(就是不帶static的成員變數和成員方法),只能訪問所屬類的靜態成員變數和靜態成員方法。因為例項成員和例項方法與特定的物件關聯.
因為static方法獨立於任何例項,因此static方法必須被實現,而不能是抽象的abstract。(即靜態方法不能是抽象的)
如果說想在不建立物件的情況下呼叫某個方法,就可以將這個方法設定為static。我們最常見的static方法就是main方法,至於為什麼main方法必須是static的,現在就很清楚了。因為程式在執行main方法的時候沒有建立任何物件,因此只有通過類名來訪問。
另外記住,即使沒有顯示地宣告為static,類的構造器實際上也是靜態方法。

3.Static程式碼塊
static程式碼塊也叫靜態程式碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM載入類時會自動執行這些靜態的程式碼塊,如果static程式碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,靜態程式碼塊沒有名字,因此不能顯式呼叫,每個程式碼塊只會在類載入時被自動執行一次(因為在虛擬機器的生命週期中一個類只被載入一次;又因為static{}是伴隨類載入執行的,所以,不管你new多少次物件例項,static{}都只執行一次)。靜態塊常用來執行類屬性的初始化.
總結如下:

  • 每個靜態程式碼塊只會在類載入時被執行一次
  • 當一個類中有多個static{}的時候,按照static{}的定義順序,從前往後執行
  • 先執行完static{}語句塊的內容,才會執行呼叫語句
  • 靜態程式碼快中不能訪問非static成員(屬性和方法)
  • 如果靜態變數在定義的時候就賦給了初值(如 static int X=100),那麼賦值操作也是在類載入的時候完成的,並且當一個類中既有static{}又有static變數的時候,同樣遵循“先定義先執行”的原則

例如:

class Test{
    public static int X=300;
    static{
      System.out.println(X);
      X=200;
      System.out.println(X);
    }
}

public class StaticBlockTest{
    public static void main(String args[]){
      System.out.println(Test.X);
    }
}

結果:程式會依次輸出300,200,200,先執行完X=300,再執行static{}語句塊。

4.Static類
一個普通類是不能宣告為靜態的,只有一個內部類才可以。這時這個宣告為靜態的內部類可以直接作為一個普通類來使用,而不需例項化一個外部類。
例如:

public class StaticCls{ 
   public static void main(String[] args){  
    OuterCls.InnerCls oi=new OuterCls.InnerCls(); 
   }
} 
class OuterCls{  
  public static class InnerCls{   
    InnerCls(){   
        System.out.println("InnerCls");  
    } 
  }
} 

結果:程式輸出InnerCls

5.import static靜態匯入:詳情見博文《靜態匯入

6.static和final一塊用表示什麼?
static final用來修飾成員變數和成員方法,可簡單理解為“全域性常量”

  • 對於變數,表示一旦給值就不可修改,並且通過類名可以訪問。
  • 對於方法,表示不可覆蓋,並且可以通過類名直接訪問。

注意:對於被static和final修飾過的例項常量,例項本身不能再改變了,但對於一些容器型別(比如,ArrayList、HashMap)的例項變數,不可以改變容器變數本身,但可以修改容器中存放的物件,這一點在程式設計中用到很多。
例如:

public class TestStaticFinal { 
        private static final String strStaticFinalVar = "aaa"; 
        private static String strStaticVar = null; 
        private final String strFinalVar = null; 
        private static final int intStaticFinalVar = 0; 
        private static final Integer integerStaticFinalVar = new Integer(8); 
        private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>(); 

        private void test() { 
                System.out.println("-------------值處理前----------\r\n"); 
                System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n"); 
                System.out.println("strStaticVar=" + strStaticVar + "\r\n"); 
                System.out.println("strFinalVar=" + strFinalVar + "\r\n"); 
                System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n"); 
                System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n"); 
                System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n"); 

                //strStaticFinalVar="哈哈哈哈";            //錯誤,final表示終態,不可以改變變數本身. 
                strStaticVar = "哈哈哈哈";                 //正確,static表示類變數,值可以改變. 
                //strFinalVar="呵呵呵呵";                  //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。 
                //intStaticFinalVar=2;                    //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。 
                //integerStaticFinalVar=new Integer(8);   //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。 
                alStaticFinalVar.add("aaa");        //正確,容器變數本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。 
                alStaticFinalVar.add("bbb");        //正確,容器變數本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。 

                System.out.println("-------------值處理後----------\r\n"); 
                System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n"); 
                System.out.println("strStaticVar=" + strStaticVar + "\r\n"); 
                System.out.println("strFinalVar=" + strFinalVar + "\r\n"); 
                System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n"); 
                System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n"); 
                System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n"); 
        } 

        public static void main(String args[]) { 
                new TestStaticFinal().test(); 
        } 
}

四.分析幾道題目
1.下面這段程式碼的輸出結果是什麼?

public class Test extends Base{

    static{
        System.out.println("test static");
    }

    public Test(){
        System.out.println("test constructor");
    }

    public static void main(String[] args) {
        new Test();
    }
}

class Base{

    static{
        System.out.println("base static");
    }

    public Base(){
        System.out.println("base constructor");
    }
}

輸出結果:
base static
test static
base constructor
Test constructor
結果分析:
先來想一下這段程式碼具體的執行過程,在執行開始,先要尋找到main方法,因為main方法是程式的入口,但是在執行main方法之前,必須先載入Test類,而在載入Test類的時候發現Test類繼承自Base類,因此會轉去先載入Base類,在載入Base類的時候,發現有static塊,便執行了static塊。在Base類載入完成之後,便繼續載入Test類,然後發現Test類中也有static塊,便執行static塊。在載入完所需的類之後,便開始執行main方法。在main方法中執行new Test()的時候會先呼叫父類的構造器,然後再呼叫自身的構造器。因此,便出現了上面的輸出結果。

2.這段程式碼的輸出結果是什麼?

public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }

    public Test() {
        System.out.println("test constructor");
    }

    public static void main(String[] args) {
        new MyClass();
    }
}

class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}


class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }

    public MyClass() {
        System.out.println("myclass constructor");
    }
}

輸出結果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
結果分析:
類似地,我們還是來想一下這段程式碼的具體執行過程。首先載入Test類,因此會執行Test類中的static塊。接著執行new MyClass(),而MyClass類還沒有被載入,因此需要載入MyClass類。在載入MyClass類的時候,發現MyClass類繼承自Test類,但是由於Test類已經被載入了,所以只需要載入MyClass類,那麼就會執行MyClass類的中的static塊。在載入完之後,就通過構造器來生成物件。而在生成物件的時候,必須先初始化父類的成員變數,因此會執行Test中的Person person = new Person(),而Person類還沒有被載入過,因此會先載入Person類並執行Person類中的static塊,接著執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接著執行MyClass中的Person person = new Person(),最後執行MyClass的構造器。

3.這段程式碼的輸出結果是什麼?

public class Test {

    static{
        System.out.println("test static 1");
    }
    public static void main(String[] args) {

    }

    static{
        System.out.println("test static 2");
    }
}

結果輸出:
test static 1
test static 2
結果分析:
雖然在main方法中沒有任何語句,但是還是會輸出,原因上面已經講述過了。另外,static塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),並且執行是按照static塊的順序執行的。

4.這段程式碼的輸出結果是什麼?

public class StaticInitialization {
    public static void main(String[] args) {
        System.out.println("Creating new Cupboard() in main");
        new Cupboard();
        System.out.println("Creating new Cupboard() in main");
        new Cupboard();
        table.f2(1);
        cupboard.f3(1);
    }
    static Table table = new Table();
    static Cupboard cupboard = new Cupboard();
}

class Bowl{
    Bowl(int marker){
        System.out.println("Bowl("+marker+")");
    }
    void f1(int marker){
        System.out.println("f1("+marker+")");
    }
}

class Table{
    static Bowl bowl1 = new Bowl(1);
    Table(){
        System.out.println("Table()");
    }
    void f2(int marker){
        System.out.println("f2("+marker+")");
    }
    static Bowl bowl2 = new Bowl(2);
}

class Cupboard{
    Bowl bowl3 = new Bowl(3);
    static Bowl bowl4 = new Bowl(4);
    Cupboard(){
        System.out.println("Cupboard");
        bowl4.f1(2);
    }
    void f3(int marker){
        System.out.println("f("+marker+")");
    }
    static Bowl bowl5 = new Bowl(5);
}

輸出結果:
Bowl(1)
Bowl(2)
Table()
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)