天天看點

Thinking in java-23 類的加載、連結和初始化

一個Java類從編輯的文本檔案如: Test.java檔案,需要經過編譯器編譯、加載-連結-初始化才能被使用。其中編譯是java編譯器将Test.java檔案編譯成Test.class位元組碼檔案,這裡主要是:load-link-initialization

1.加載load

Java中類的加載是通過類加載器完成的。類加載器最終要完成的功能是定義一個java類,即把java位元組碼檔案轉換為JVM中java.lang.Class對象。

  • bootstrap classloader: 啟動類加載器是由JVM原生代碼實作的,
  • user-defined classloader:使用者自定義的類加載器都繼承自java.lang.ClassLoader類。

Class.forName(“SomeClass”)和ClassLoader.getSystemClassLoader().loadClass(“SomeClass”):

這裡有相關詳細連結。

這2種方式都是動态地裝載類到classpath的方法,但兩者在1).是否在裝載時初始化; 2).從哪裡被裝載;這兩點問題上有所不同。

Class.forName(“SomeClass”)

  • 預設地,類在裝載時被初始化。這意味着類中靜态變量被初始化了。
  • 其次,類從目前類裝載器中被裝載。當調用Class.forName()裝載JDBC驅動類時,該驅動類被裝載到你所調用的類裝載器那裡。簡言之,被裝載類被裝載到調用者的類裝載器中。
  • Class.forName(className, true, currentLoader), 這是一個被重載的方法。可以選擇性地加入第二或第三個參數來改變一些行為。

ClassLoader.loadClass()

  • 預設地,類在裝載時并沒有被初始化。類在classpath中被裝載,并可用;但變量隻有在被調用者調用時才被初始化。
  • 該方法的一個優勢是,可以選擇裝載類到特定的類裝載器中。
package com.fqyuan.thinking;

public class Person {

    public static void main(String[] args) {
        ClassLoader cl1 = Thread.currentThread().getContextClassLoader();
        System.out.println(cl1.toString());
        ClassLoader cl2 = ClassLoader.getSystemClassLoader();
        System.out.println(cl2.toString());

        try {
            Class<?> c1 = cl1.loadClass("com.fqyuan.thinking.ForLoadTest");
            System.out.println(c1.hashCode() + " \n" + c1.getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            Class<?> c2 = Class.forName("com.fqyuan.thinking.ForLoadTest");
            System.out.println(c2.hashCode() + " \n" + c2.getClassLoader());
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

//Running result:
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$AppClassLoader@2a139a55
 
sun.misc.Launcher$AppClassLoader@2a139a55
Static Initializer Called!!
 
sun.misc.Launcher$AppClassLoader@2a139a55
           

2.連結link

JLS有詳細解釋。

1. Verification: 驗證保證了二進制的類或接口結構上是正确的。

2. Preparation:包括建立類或接口的靜态域,并把這些域初始化到預設值。

3. Resolution:将運作時常量池中的符号引用動态解析成實際值的方式。即:保證目前類的引用被正确得找到。

3.初始化initialization

注意,這裡是Class的初始化,而不是instance對象執行個體的初始化。當Java類第一次被真正使用時,JVM負責初始化該類。實際做的工作是:

  • 執行靜态代碼塊;
  • 初始化靜态資料域
package fqy.iss.thinking.reuse;

import static fqy.iss.utils.Print.print;

class Insect
{
    private int i = ;
    protected int j;

    Insect()
    {
        print("i = " + i + ", j = " + j);
        j = ;
    }

    private static int x1 = printInit("static Insect.x1 initialized");

    static int printInit(String s)
    {
        print(s);
        return ;
    }
}

public class Beetle extends Insect
{
    private int k = printInit("Beetle.k initialized");

    public Beetle()
    {
        print("k = " + k);
        print("j = " + j);
    }

    private static int x2 = printInit("static Beetle.x2 initialized");

    public static void main(String[] args)
    {
        print("Beetle constructor");
        Beetle b = new Beetle();
    }

}

//Running result
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = , j = 
Beetle.k initialized
k = 
j = 
           

産生上述結果分析;

  1. 首先按照從父類到基類的順序,類被加載。完成靜态資料域和靜态代碼塊的執行。然後類加載器ClassLoader就把Class傳回,故而出現了運作結果中的前2行。
  2. 然後開始執行main() 方法。main()方法中new了基類對象。實際執行順序是:先初始化父類中的執行個體方法,再執行父類構造方法;再初始化子類中的執行個體方法,然後執行字類的構造方法。