反射

什么是反射谴古?
反射就是Reflection,Java反射是指程序運行期間可以拿到一個對象的一切信息。
正常情況下,如果我們要調(diào)用一個對象的方法经磅,或者訪問一個對象的字段,通常會傳入對象實例:

// Main.java
import com.itranswarp.learnjava.Person;

public class Main {
    String getFullName(Person p) {
        return p.getFirstName() + " " + p.getLastName();
    }
}

jdk編譯后生成 String.class文件钮追,
編譯Java源碼生成“.class”文件里面實際是字節(jié)碼预厌,而不是可以直接執(zhí)行的機器碼。在運行時元媚,JVM會通過類加載器加載字節(jié)碼轧叽,解釋或者編譯執(zhí)行.

class是由JVM在執(zhí)行過程中動態(tài)加載的。JVM在第一次讀取到一種class類型時刊棕,將其加載進(jìn)內(nèi)存炭晒。
每加載一種class,JVM就為其創(chuàng)建一個Class類型的實例甥角,并關(guān)聯(lián)起來网严。注意:這里的Class類型是一個名叫Class的class。它長這樣:

public final class Class {
    private Class() {}
}
// java.lang包下:
package java.lang;
public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement {
    private Class(ClassLoader var1) {
        this.classLoader = var1;
    }

String類為例嗤无,當(dāng)JVM加載String類時屿笼,它
首先讀取String.class文件到內(nèi)存,
然后翁巍,為String類創(chuàng)建一個Class實例并關(guān)聯(lián)起來:

Class cls = new Class(String);

這個Class實例是JVM內(nèi)部創(chuàng)建的驴一,如果我們查看JDK源碼,可以發(fā)現(xiàn)Class類的構(gòu)造方法是private灶壶,只有JVM能創(chuàng)建Class實例肝断,我們自己的Java程序是無法創(chuàng)建Class實例的。

所以驰凛,JVM持有的每個Class實例都指向一個數(shù)據(jù)類型(class或interface):
┌────────────────┐
│ Class Instance │──────> String
├───────────── ┤
│name = "java.lang.String" │
└──────────────── ┘
┌─────────────────┐
│ Class Instance │──────> Random
├─────────────────┤
│name = "java.util.Random" │
└─────────────────┘
┌──────────────────┐
│ Class Instance │──────> Runnable
├──────────────────┤
│name = "java.lang.Runnable"│
└──────────────────┘

一個Class實例包含了該class的所有完整信息:

┌──────────────────┐
│ Class Instance │──────> String
├─────────────────┤
│name = "java.lang.String" │
├──────────────────┤
│package = "java.lang" │
├──────────────────┤
│super = "java.lang.Object" │
├──────────────────┤
│interface = CharSequence... │
├──────────────────┤
│field = value[],hash,... │
├─────────────────┤
│method = indexOf()... │
└──────────────────┘
由于JVM為每個加載的class創(chuàng)建了對應(yīng)的Class實例胸懈,并在實例中保存了該class的所有信息,包括類名恰响、包名趣钱、父類、實現(xiàn)的接口胚宦、所有方法首有、字段等燕垃,因此,如果獲取了某個Class實例井联,我們就可以通過這個Class實例獲取到該實例對應(yīng)的class的所有信息卜壕。

這種通過Class實例獲取class信息的方法稱為反射(Reflection)。

如何獲取一個class的Class實例烙常?有三個方法:
1轴捎、靜態(tài)方法 XXX.class
直接通過一個class的靜態(tài)變量class獲取,這個方法有點像對象訪問屬性的方法,可以直接.獲取Class實例蚕脏。

Class class1 = String.class;

2侦副、getClass()方法
如果我們有一個實例變量,可以通過該實例變量提供的getClass()方法獲韧毡蕖:

String s = "Hello";
Class cls = s.getClass();

3跃洛、Class.forName("XXX")獲取
方法三:如果知道一個class的完整類名,可以通過靜態(tài)方法Class.forName()獲戎找椤:

Class cls = Class.forName("java.lang.String");

因為Class實例在JVM中是唯一的,所以葱蝗,上述方法獲取的Class實例是同一個實例穴张。可以用==比較兩個Class實例:
以上三種方法指向的都是同一個class两曼,所以可以用==來比較皂甘,只是可以精確地判斷數(shù)據(jù)類型,但不能作子類型比較悼凑。JVM在執(zhí)行Java程序的時候偿枕,并不是一次性把所有用到的class全部加載到內(nèi)存户辫,而是動態(tài)加載,在運行期根據(jù)條件加載不同的實現(xiàn)類墓塌。

Class cls1 = String.class;

String s = "Hello";
Class cls2 = s.getClass();

boolean sameClass = cls1 == cls2; // true

注意一下Class實例比較和instanceof的差別:

Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true奥额,因為n是Integer類型
boolean b2 = n instanceof Number; // true,因為n是Number類型的子類

boolean b3 = n.getClass() == Integer.class; // true垫挨,因為n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因為Integer.class!=Number.class
public final class Integer extends Number implements Comparable<Integer> {

instanceof不但匹配指定類型九榔,還匹配指定類型的子類涡相。而用==判斷class實例可以精確地判斷數(shù)據(jù)類型谜诫,但不能作子類型比較喻旷。
通常情況下,我們應(yīng)該用instanceof判斷數(shù)據(jù)類型且预,因為面向抽象編程的時候,我們不關(guān)心具體的子類型遍尺。只有在需要精確判斷一個類型是不是某個class的時候涮拗,我們才使用==判斷class實例三热。
因為反射的目的是為了獲得某個實例的信息。因此呐能,當(dāng)我們拿到某個Object實例時抑堡,我們可以通過反射獲取該Object的class信息:

void printObjectInfo(Object obj) {
    Class cls = obj.getClass();
}

要從Class實例獲取獲取的基本信息,參考下面的代碼:

public class Main {
    public static void main(String[] args) {
        printClassInfo("".getClass());
        printClassInfo(Runnable.class);
        printClassInfo(java.time.Month.class);
        printClassInfo(String[].class);
        printClassInfo(int.class);
    }

    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getName());
        System.out.println("Simple name: " + cls.getSimpleName());
        if (cls.getPackage() != null) {
            System.out.println("Package name: " + cls.getPackage().getName());
        }
        System.out.println("is interface: " + cls.isInterface());
        System.out.println("is enum: " + cls.isEnum());
        System.out.println("is array: " + cls.isArray());
        System.out.println("is primitive: " + cls.isPrimitive());
    }
}

注意到數(shù)組(例如String[])也是一種Class偎漫,而且不同于String.class有缆,它的類名是[Ljava.lang.String。此外通危,JVM為每一種基本類型如int也創(chuàng)建了Class灌曙,通過int.class訪問。

如果獲取到了一個Class實例在刺,我們就可以通過該Class實例來創(chuàng)建對應(yīng)類型的實例:

// 獲取String的Class實例:
Class cls = String.class;
// 創(chuàng)建一個String實例:
String s = (String) cls.newInstance();

上述代碼相當(dāng)于new String()。通過Class.newInstance()可以創(chuàng)建類實例魄幕,它的局限是:只能調(diào)用public的無參數(shù)構(gòu)造方法。帶參數(shù)的構(gòu)造方法坛芽,或者非public的構(gòu)造方法都無法通過Class.newInstance()被調(diào)用翼抠。

動態(tài)加載

JVM在執(zhí)行Java程序的時候,并不是一次性把所有用到的class全部加載到內(nèi)存活喊,而是第一次需要用到class時才加載量愧。例如:

// Main.java
public class Main {
    public static void main(String[] args) {
        if (args.length > 0) {
            create(args[0]);
        }
    }

    static void create(String name) {
        Person p = new Person(name);
    }
}

當(dāng)執(zhí)行Main.java時,由于用到了Main煞烫,因此软棺,JVM首先會把Main.class加載到內(nèi)存尤勋。然而,并不會加載Person.class瘦棋,除非程序執(zhí)行到create()方法,JVM發(fā)現(xiàn)需要加載Person類時赌朋,才會首次加載Person.class篇裁。如果沒有執(zhí)行create()方法,那么Person.class根本就不會被加載达布。

這就是JVM動態(tài)加載class的特性黍聂。

動態(tài)加載class的特性對于Java程序非常重要身腻。利用JVM動態(tài)加載class的特性匹厘,我們才能在運行期根據(jù)條件加載不同的實現(xiàn)類。例如愈诚,Commons Logging總是優(yōu)先使用Log4j,只有當(dāng)Log4j不存在時尤溜,才使用JDK的logging汗唱。利用JVM動態(tài)加載特性,大致的實現(xiàn)代碼如下:

// Commons Logging優(yōu)先使用Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
    factory = createLog4j();
} else {
    factory = createJdkLog();
}

boolean isClassPresent(String name) {
    try {
        Class.forName(name);
        return true;
    } catch (Exception e) {
        return false;
    }
}

這就是為什么我們只需要把Log4j的jar包放到classpath中授霸,Commons Logging就會自動使用Log4j的原因际插。

小結(jié)

JVM為每個加載的classinterface創(chuàng)建了對應(yīng)的Class實例來保存class及interface的所有信息;

獲取一個class對應(yīng)的Class實例后辛辨,就可以獲取該class的所有信息瑟枫;

通過Class實例獲取class信息的方法稱為反射(Reflection);

JVM總是動態(tài)加載class僻焚,可以在運行期根據(jù)條件來控制加載class膝擂。

訪問字段

對任意的一個Object實例,只要我們獲取了它的Class狞山,就可以獲取它的一切信息叉寂。

我們先看看如何通過Class實例獲取字段信息。Class類提供了以下幾個方法來獲取字段:

  • Field getField(name):根據(jù)字段名獲取某個public的field(包括父類)
  • Field getDeclaredField(name):根據(jù)字段名獲取當(dāng)前類的某個field(不包括父類)
  • Field[] getFields():獲取所有public的field(包括父類)
  • Field[] getDeclaredFields():獲取當(dāng)前類的所有field(不包括父類)
    我們來看一下示例代碼:

XXX.class
類加載器將.class文件加載到內(nèi)存伊约,代碼區(qū)
Code Segment

public <A extends Annotation> A[] getAnnotationsByType(Class<A> var1) {

java中有如下幾種類加載器:
BootstrapClassLoader 最核心的屡律,load其他的classLoader, 其他的classloader再load其他的Class

  • implemented by native language (C/C++/匯編。區(qū)別于JRE版本搏讶。其他loader都是java寫的)(rt.java,沒有名字)
  • load the core public <A extends Annotation> A[] getAnnotationsByType(Class<A> var1) {
    classes of JDK

ExtesionClassLoader 負(fù)責(zé)JDK的擴展類

  • loader the class from jre/lib/ext
    ApplicationClassLoader

  • load user-define classes

  • ClassLoader.getSystemClassLoader() == application class loader
    other class loaders

  • SecureClassLoader

  • URLClassLoader (2/3的父類)

  • user-define ClassLoader

當(dāng)前裝在這個類的classLoader
parent不是指繼承霍殴,是指那個classLoader引用

jdk classLoader被appClassLoader,找上一層的妒蔚,如果上一層加載了肴盏,就不會再加載帽衙,
安全性好!

如果自己寫java.lang.String, 破壞性強厉萝,但因為上一層加載谴垫,就不會加載手寫的這個String.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弹渔,一起剝皮案震驚了整個濱河市溯祸,隨后出現(xiàn)的幾起案子焦辅,更是在濱河造成了極大的恐慌,老刑警劉巖剃根,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件前方,死亡現(xiàn)場離奇詭異,居然都是意外死亡抒线,警方通過查閱死者的電腦和手機渣慕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門逊桦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來强经,“玉大人,你說我怎么就攤上這事宝穗÷氡” “怎么了转砖?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵府蔗,是天一觀的道長姓赤。 經(jīng)常有香客問我,道長蝌焚,這世上最難降的妖魔是什么只洒? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任毕谴,我火速辦了婚禮涝开,結(jié)果婚禮上舀武,老公的妹妹穿的比我還像新娘。我一直安慰自己衷旅,他們只是感情好柿顶,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著聂薪,像睡著了一般藏澳。 火紅的嫁衣襯著肌膚如雪业崖。 梳的紋絲不亂的頭發(fā)上蓄愁,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天妇斤,我揣著相機與錄音站超,去河邊找鬼顷编。 笑死剑刑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播施掏,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钮惠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了七芭?” 一聲冷哼從身側(cè)響起素挽,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狸驳,沒想到半個月后预明,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡耙箍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年撰糠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辩昆。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡阅酪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汁针,到底是詐尸還是另有隱情术辐,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布施无,位于F島的核電站辉词,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏猾骡。R本人自食惡果不足惜较屿,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卓练。 院中可真熱鬧隘蝎,春花似錦、人聲如沸襟企。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顽悼。三九已至曼振,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔚龙,已是汗流浹背冰评。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留木羹,地道東北人甲雅。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓解孙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親抛人。 傳聞我的和親對象是個殘疾皇子弛姜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容