【轉載】Java系列筆記(1),轉載java系列筆記

來源:互聯網
上載者:User

【轉載】Java系列筆記(1),轉載java系列筆記
Java系列筆記(1) - Java 類載入與初始化

原文地址:http://www.cnblogs.com/zhguang/p/3154584.html

目錄

類載入器

在瞭解Java的機制之前,需要先瞭解類在JVM(Java虛擬機器)中是如何載入的,這對後面理解java其它機制將有重要作用。

每個類編譯後產生一個Class對象,儲存在.class檔案中,JVM使用類載入器(Class Loader)來載入類的位元組碼檔案(.class),類載入器實質上是一條類載入器鏈,一般的,我們只會用到一個原生的類載入器,它只載入Java API等可信類,通常只是在本地磁碟中載入,這些類一般就夠我們使用了。如果我們需要從遠程網路或資料庫中下載.class位元組碼檔案,那就需要我們來掛載額外的類載入器。

一般來說,類載入器是按照樹形的階層組織的,每個載入器都有一個父類載入器。另外,每個類載入器都支援代理模式,即可以自己完成Java類的載入工作,也可以代理給其它類載入器。

類載入器的載入順序有兩種,一種是父類優先策略,一種是是自己優先策略,父類優先策略是比較一般的情況(如JDK採用的就是這種方式),在這種策略下,類在載入某個Java類之前,會嘗試代理給其父類載入器,只有當父類載入器找不到時,才嘗試自己去載入。自己優先的策略與父類優先相反,它會首先嘗試自己載入,找不到的時候才要父類載入器去載入,這種在web容器(如tomcat)中比較常見。

動態載入

不管使用什麼樣的類載入器,類,都是在第一次被用到時,動態載入到JVM的。這句話有兩層含義:

需要區分載入和初始化的區別,載入了一個類的.class檔案,並不意味著該Class對象被初始化,事實上,一個類的初始化包括3個步驟:

  • 載入(Loading),由類載入器執行,尋找位元組碼,並建立一個Class對象(只是建立);
  • 連結(Linking),驗證位元組碼,為靜態域分配儲存空間(只是分配,並不初始化該儲存空間),解析該類建立所需要的對其它類的應用;
  • 初始化(Initialization),首先執行靜態初始化塊static{},初始化靜態變數,執行靜態方法(如構造方法)。

連結

Java在載入了類之後,需要進行連結的步驟,連結簡單地說,就是將已經載入的java二進位程式碼群組合到JVM運行狀態中去。它包括3個步驟:

初始化

注 意:在《Java編程思想》中,說static{}子句是在類第一次載入時執行且執行一次(可能是筆誤或翻譯錯誤,因為此書的例子顯示static是在第 一次初始化時執行的),《Java深度曆險》中說 static{}是在第一次執行個體化時執行且執行一次,這兩種應該都是錯誤的,static{}是在第一次初始化時執行,且只執行一次;用下面的代碼可以判 定出來:

package myblog.classloader;/** * @project MyBlog * @create 2013年6月18日 下午7:00:45 * @version 1.0.0 * @author 張廣 */public class Toy {        private String name;        public static final int price=10;                static {                System.out.println("Initializing");        }        Toy() {                System.out.println("Building");        }        Toy(String name) {                this.setName(name);        }        public static String playToy(String player) {                String msg = buildMsg(player);                System.out.println(msg);                return msg;        }        private String buildMsg(String player) {                String msg = player + " plays " + name;                return msg;        }}  // 對上面的類,執行下面的代碼:  Class c = Class.forName("myblog.rtti.Toy");  // c.newInstance();

可以看到,不執行個體化,只執行forName初始化時,仍然會執行static{}子句,但不執行構造方法,因此輸出的只有Initializing,沒有Building。

關於初始化,@阿春阿曉 在本文的評論中給出了很詳細的情境,感謝@阿春阿曉:

根據java虛擬機器規範,所有java虛擬機器實現必須在每個類或介面被java程式首次主動使用時才初始化。

主動使用有以下6種:
1)建立類的執行個體
2)訪問某個類或者介面的靜態變數,或者對該靜態變數賦值(如果訪問靜態編譯時間常量(即編譯時間可以確定值的常量)不會導致類的初始化)
3)調用類的靜態方法
4)反射(Class.forName(xxx.xxx.xxx))
5)初始化一個類的子類(相當於對父類的主動使用),不過直接通過子類引用父類元素,不會引起子類的初始化(參見樣本6)
6)Java虛擬機器被標明為啟動類的類(包含main方法的)

類與介面的初始化不同,如果一個類被初始化,則其父類或父介面也會被初始化,但如果一個介面初始化,則不會引起其父介面的初始化。

樣本

1,通過上面的講解,將可以理解下面的程式(下面的程式部分來自於《Java編程思想》):

class Toy {        static {                System.out.println("Initializing");// 靜態子句,只在類第一次被載入並初始化時執行一次,而且只執行一次        }        Toy() {                System.out.println("Building");// 構造方法,在每次聲明新對象時載入        }}

對上面的程式段,第一次調用Class.forName("Toy"),將執行static子句;如果在之後執行new Toy()都只執行構造方法。

2,需要注意newInstance()方法

Class cc = Class.forName("Toy");//獲得類(注意,需要使用含包名的全限定名)Toy toy=(Toy)cc.newInstance(); //相當於new一個對象,但Gum類必須有預設構造方法(無參)

3,用類字面常量 .class和Class.forName都可以建立對類的應用,但是不同點在於,用Gum.class建立Class對象的應用時,不會自動初始化該Class對象(static子句不會執行)

public class TestToy {        public static void main(String[] args) {                // try {                // Class c = Class.forName("myblog.classloader.Toy");                // } catch (ClassNotFoundException e) {                // e.printStackTrace();                // }                Class c = Toy.class; // 不會輸出任何值        }}

使用Toy.class是在編譯期執行的,因此在編譯時間必須已經有了Toy的.class檔案,不然會編譯失敗,這與 Class.forName("myblog.classloader.Toy")不同,後者是運行時動態載入。

但是,如果該main方法是直接寫在Toy類中,那麼調用Toy.class,會引起初始化,並輸出Initializing,原因並不是Toy.class引起的,而是該類中含有啟動方法main,該方法會導致Toy的初始化。

 4,編譯時間常量。回到完整的類Toy,如果直接輸出:System.out.println(Toy.price),會發現static子句和構造方法都沒有被執行,這是因為Toy中,常量price被static final限定,這樣的常量叫做編譯時間常量,對於這種常量,不需要初始化就可以讀取。
編譯時間常量必須滿足3個條件:static的,final的,常量。

下面幾種都不是編譯時間常量,對它們的應用,都會引起類的初始化:

static int a;final  int b;static final  int c= ClassInitialization.rand.nextInt(100);static final  int d;static {    d=5;}

5,static塊的本質。注意下面的代碼:

class StaticBlock {        static final int c = 3;        static final int d;        static int e = 5;        static {                d = 5;                e = 10;                System.out.println("Initializing");        }        StaticBlock() {                System.out.println("Building");        }}public class StaticBlockTest {        public static void main(String[] args) {                System.out.println(StaticBlock.c);                System.out.println(StaticBlock.d);                System.out.println(StaticBlock.e);        }}

這段代碼的輸出是什麼呢?Initialing在c、d、e之前輸出,還是在之後?e輸出的是5還是10?

執行一下,結果為:

3
Initializing
5
10

答案是3最先輸出,Intializing隨後輸出,e輸出的是10,為什麼呢?

原因是這樣的:輸出c時,由於c是編譯時間常量,不會引起類初始化,因此直接輸出,輸出d時,d不是編譯時間常量,所以會引起初始化操作,即static塊的執行,於是d被賦值為5,e被賦值為10,然後輸出Initializing,之後輸出d為5,e為10。

但e為什麼是10呢?原來,JDK會自動為e的初始化建立一個static塊(參考:http://www.java3z.com/cwbwebhome/article/article8/81101.html?id=2497),所以上面的代碼等價於:

class StaticBlock {        static final int d;        static int e;                static {           e=5;         }        static {            d = 5;            e = 10;            System.out.println("Initializing");        }        StaticBlock() {            System.out.println("Building");        }}    

可見,按順序執行,e先被初始化為5,再被初始化為10,於是輸出了10。

類似的,容易想到下面的代碼:

class StaticBlock {        static {                d = 5;                e = 10;                System.out.println("Initializing");        }        static final int d;        static int e = 5;        StaticBlock() {                System.out.println("Building");        }}

在這段代碼中,對e的聲明被放到static塊後面,於是,e會先被初始化為10,再被初始化為5,所以這段代碼中e會輸出為5。

6,當訪問一個Java類或介面的靜態域時,只有真正聲明這個域的類或介面才會被初始化(《Java深度曆險》)

/** * 例子來源於《Java深度曆險》第二章 * @author 張廣 * */class B {        static int value = 100;        static {                System.out.println("Class B is initialized");// 輸出        }}class A extends B {        static {                System.out.println("Class A is initialized"); // 不輸出        }}public class SuperClassTest {        public static void main(String[] args) {                System.out.println(A.value);// 輸出100        }}

在該例子中,雖然通過A來引用了value,但value是在父類B中聲明的,所以只會初始化B,而不會引起A的初始化。

說明

筆者在開發過程中發現自己基礎太薄弱,讀書時除了系統學習了一下Java的基礎文法和用法、一點簡單的資料結構和設計模式之外,再無深入系統的學習,而工作中的學習也是東晃一槍西晃一槍,不夠紮實和系統。想到一個學習方法:學到的東西能夠系統的表達出來,才說明你學到手了;於是,筆者決定邊學邊寫,將學到的東西以部落格的形式表達出來。

本文檔會因學習的深入或錯誤的訂正而持續更新。

《Java系列筆記》是本人對Java的應用和學習過程中的筆記,按知識點分章節,寫這一系列筆記的目的是學習,由於筆者是邊學編寫的,水平有限,文中必定有疏漏之處,歡迎斧正。

文中多有參考前輩的書籍和部落格之處,原則上不會原文引用,而是加入自己的理解改造之,如果有引用,必會註明出處,如有疑問,請聯絡:daniel.zhguang@gmail.com

更新記錄

2013年6月25日:發表;

2013年6月26日:加入@阿春阿曉的評論:引起初始化的6種情境;

2013年6月28日:加入連結和初始化的內容,加入樣本6

本節參考資料

JAVA編程思想,第14章

Java深度曆險

Java中static塊的本質:http://www.java3z.com/cwbwebhome/article/article8/81101.html?id=2497

 java類的裝載(Loading)、連結(Linking)和初始化(Initialization):http://blog.csdn.net/biaobiaoqi/article/details/6909141

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.