反射


反射簡介

反射允許我們在程序運(yùn)行時(shí)獲取和使用類的信息。


Class 對(duì)象

Java程序運(yùn)行時(shí),用Class對(duì)象表示類型信息,它包含了與類相關(guān)的信息。類是程序的一部分,每個(gè)類都有一個(gè)Class對(duì)象,也就是說,當(dāng)一個(gè)類被編譯之后,就會(huì)產(chǎn)生一個(gè)Class對(duì)象,并保存在.class文件中。當(dāng)程序創(chuàng)建第一個(gè)對(duì)類的靜態(tài)成員的引用時(shí),JVM就會(huì)用類加載器加載Class對(duì)象,當(dāng)該類的Class對(duì)象被加載入內(nèi)存后,就可以使用它來創(chuàng)建實(shí)例對(duì)象。

獲取Class對(duì)象引用的方式:

  • 通過Class.forName() 方法來加載類并獲取Class對(duì)象的引用并初始化該類,該方法的參數(shù)必須為類的全限定名(包含包名)
try {
    Class.forName("Person");
} catch (Exception ex) {
    ex.printStackTrace();
}
  • 如果已經(jīng)獲得該類的實(shí)例,可通過該實(shí)例調(diào)用getClass()方法獲取Class對(duì)象的引用
Person person = new Person();
Class clazz = person.getClass();
  • 使用類字面常量,該方式僅僅只是拿到Class對(duì)象的引用,并沒有進(jìn)行初始化
Class clazz = Person.class;

對(duì)于基本數(shù)據(jù)類型,同樣存在類字面常量,而且,在對(duì)應(yīng)的包裝類中有一個(gè)字段TYPE作為基本數(shù)據(jù)類型的類字面常量的引用,比如,對(duì)于 int 來說,其字面常量為int.class,它與Integer.TYPE等價(jià),但要注意的是,它們與Integer.class是兩回事。
為了使用類而做的準(zhǔn)備工作有三個(gè)步驟:
1.加載。由類加載器執(zhí)行,查找字節(jié)碼,并通過字節(jié)碼創(chuàng)建一個(gè)Class對(duì)象。
2.鏈接。在這個(gè)階段將驗(yàn)證類中的字節(jié)碼,為靜態(tài)域分配存儲(chǔ)空間,如果必須的話,還要解析這個(gè)類創(chuàng)建的對(duì)其他類的引用
3.初始化。如果該類有超類,則對(duì)其初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊。初始化被延遲到了對(duì)靜態(tài)方法(在這種情況下,構(gòu)造器可以視為靜態(tài)的方法)或者非常數(shù)靜態(tài)域進(jìn)行首次引用的時(shí)候才執(zhí)行。例子如下:

import java.util.Random;

class Initable {
    static final int staticFinal = 47;
    static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);

    static {
        System.out.println("Initializing Initable");
    }
}

class Initable2 {
    static int staticNonFinal = 147;
    static {
        System.out.println("Initializing Initable2");
    }
}

class Initable3 {
    static int staticNonFinal = 74;
    static {
        System.out.println("Initializing Initable3");
    }
}

public class ClassInitialization {

    public static Random rand = new Random(47);

    public static void main(String[] args) throws Exception {

        //這里不會(huì)觸發(fā)初始化
        Class initable = Initable.class;
        System.out.println("After creating Initable ref");

        //這里也不會(huì)觸發(fā)初始化,因?yàn)橹皇且镁幾g期常量
        System.out.println(Initable.staticFinal);

        //這里觸發(fā)初始化,staticFinal2并不是一個(gè)編譯期常量
        System.out.println(Initable.staticFinal2);

        //這里同樣會(huì)觸發(fā)初始化, 因?yàn)槿绻鹲tatic域不是final的情況下,對(duì)它進(jìn)行訪問時(shí)必須要先進(jìn)行鏈接(分配存儲(chǔ)空間)和初始化(初始化存儲(chǔ)空間)
        System.out.println(Initable2.staticNonFinal);

        //Class.forName()方法在加載類后會(huì)進(jìn)行初始化
        Class.forName("Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);

    }

}

輸出結(jié)果如下:

After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74

Class對(duì)象一些常用方法:

  • newInstance()
    創(chuàng)建新實(shí)例對(duì)象,使用該方法來創(chuàng)建的類,必須帶有默認(rèn)的構(gòu)造器,否則會(huì)拋出異常InstantiationException
  • getName(), getSimpleName(), getCanonicalName()
    getName() 獲取全限定的類名,getSimpleName() 獲取不含包名的類名, getCanonicalName() 也是獲取全限定的類名, 對(duì)于普通的類來說,兩者結(jié)果基本是一樣的,但是對(duì)于一些特殊的類,比如數(shù)組和內(nèi)部類等,兩者的結(jié)果有所差異。
public class Main {

    public static void main(String[] args) {

        Integer[] array = new Integer[5];
        InnerClass innerClass = new InnerClass();

        System.out.println(array.getClass().getName());
        System.out.println(array.getClass().getCanonicalName());

        System.out.println(innerClass.getClass().getName());
        System.out.println(innerClass.getClass().getCanonicalName());

    }

    private static class InnerClass {

    }

}

結(jié)果如下:

[Ljava.lang.Integer;
java.lang.Integer[]
Main$InnerClass
Main.InnerClass

  • isInstance()
    instanceof 關(guān)鍵字作用一樣,但是更靈活, instanceof 需要顯示地指定類型,而isInstance() 則不需要,只要拿到Class對(duì)象的引用即可。
    對(duì)于兩個(gè)Class對(duì)象的比較,可以使用==也可以使用equals(),但是它們與isIntanceof以及isInstance()有所不同,==equals()不考慮繼承
class Base {}

class Derived extends Base {}

public class ClassCompareTest {


    static void test(Object x) {
        Class clazz = x.getClass();
        String className = clazz.getSimpleName();
        System.out.println("測試" + clazz);
        System.out.println("x instanceof Base [" + (x instanceof Base) + "]");
        System.out.println("x instanceof Derived [" + (x instanceof Derived) + "]");
        System.out.println("Base.class.isInstance(x) [" + (Base.class.isInstance(x)) + "]");
        System.out.println("Derived.class.isInstance(x) [" + (Derived.class.isInstance(x)) + "]");
        System.out.println(className + ".class == Base.class [" + (clazz == Base.class) + "]");
        System.out.println(className + ".class == Derived.class [" + (clazz == Derived.class) + "]");
        System.out.println(className + ".class.equals(Base.class) [" + (clazz.equals(Base.class)) + "]");
        System.out.println(className + ".class.equals(Derived.class) [" + (clazz.equals(Derived.class)) + "]");
    }

    public static void main(String[] args) {
        test(new Base());
        test(new Derived());
    }
}

輸出結(jié)果為:

測試class Base
x instanceof Base [true]
x instanceof Derived [false]
Base.class.isInstance(x) [true]
Derived.class.isInstance(x) [false]
Base.class == Base.class [true]
Base.class == Derived.class [false]
Base.class.equals(Base.class) [true]
Base.class.equals(Derived.class) [false]
測試class Derived
x instanceof Base [true]
x instanceof Derived [true]
Base.class.isInstance(x) [true]
Derived.class.isInstance(x) [true]
Derived.class == Base.class [false]
Derived.class == Derived.class [true]
Derived.class.equals(Base.class) [false]
Derived.class.equals(Derived.class) [true]


類成員

通過Class對(duì)象可以獲取類運(yùn)行時(shí)的類成員信息,類成員用Member接口表示,該接口的實(shí)現(xiàn)類有Field、Method、 Constractor。

Field類

  • Field對(duì)象的獲取
    Field表示類的成員變量,可以使用Class對(duì)象的getFields()getDeclaredFields(),兩者的區(qū)別在于前者只能拿到所有從父類繼承來的public成員變量以及自己的public成員變量,而后者只能拿在本類內(nèi)聲明的所有成員變量,不管修飾符是什么。另外還有getField()getDeclaredField()用來返回特定的成員變量,區(qū)別和前面一樣。對(duì)于Method類以及Constractor類也同理。
  • 獲取成員變量的類型
    可以使用getType()getGenericType()獲取成員變量的類型,兩者的區(qū)別:
    • 前者返回的是變量類型的Class對(duì)象引用,不包含泛型信息
    • 當(dāng)變量類型沒有泛型時(shí),后者結(jié)果與前者一致。當(dāng)變量類型有泛型時(shí),返回結(jié)果是一個(gè)Type接口的類型,包含泛型信息。
      假設(shè)有這樣一個(gè)類:
    public class Student {
      private String name;
      private List<String> courses;
    }
    
    運(yùn)行下面例子:
          Class clazz = Student.class;
    
          Field[]  fields = clazz.getDeclaredFields();
    
          for (Field field: fields) {
              System.out.println("getType(): " + field.getType());
              System.out.println("getGenericType(): " + field.getGenericType());
          }
    

結(jié)果如下:

getType(): class java.lang.String
getGenericType(): class java.lang.String
getType(): interface java.util.List
getGenericType(): java.util.List<java.lang.String>

參考:《Java編程思想(第4版)》Bruce Eckel 著

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容