公共技術點之 Java 反射 Reflection

1. 了解 Java 中的反射

1.1 什么是 Java 的反射

Java 反射是可以讓我們在運行時獲取類的函數(shù)扬卷、屬性颤介、父類期贫、接口等 Class 內(nèi)部信息的機制跟匆。通過反射還可以讓我們在運行期實例化對象,調(diào)用方法通砍,通過調(diào)用 get/set 方法獲取變量的值玛臂,即使方法或?qū)傩允撬接械牡囊部梢酝ㄟ^反射的形式調(diào)用,這種“看透 class”的能力被稱為內(nèi)省封孙,這種能力在框架開發(fā)中尤為重要迹冤。 有些情況下,我們要使用的類在運行時才會確定虎忌,這個時候我們不能在編譯期就使用它泡徙,因此只能通過反射的形式來使用在運行時才存在的類(該類符合某種特定的規(guī)范,例如 JDBC)膜蠢,這是反射用得比較多的場景堪藐。

還有一個比較常見的場景就是編譯時我們對于類的內(nèi)部信息不可知,必須得到運行時才能獲取類的具體信息挑围。比如 ORM 框架礁竞,在運行時才能夠獲取類中的各個屬性,然后通過反射的形式獲取其屬性名和值贪惹,存入數(shù)據(jù)庫苏章。這也是反射比較經(jīng)典應用場景之一。

1.2 Class 類

那么既然反射是操作 Class 信息的,Class 又是什么呢?

當我們編寫完一個 Java 項目之后枫绅,所有的 Java 文件都會被編譯成一個.class 文件泉孩,這些 Class 對象承載了這個類型的父類、接口并淋、構(gòu)造函數(shù)寓搬、方法、屬性等原始信息县耽,這些 class 文件在程序運行時會被 ClassLoader 加載到虛擬機中句喷。當一個類被加載以后,Java 虛擬機就會在內(nèi)存中自動產(chǎn)生一個 Class 對象兔毙。我們通過 new 的形式創(chuàng)建對象實際上就是通過這些 Class 來創(chuàng)建唾琼,只是這個過程對于我們是不透明的而已。

下面的章節(jié)中我們會為大家演示反射的一些常用 api澎剥,從代碼的角度理解反射锡溯。

2. 反射 Class 以及構(gòu)造對象

2.1 獲取 Class 對象

在你想檢查一個類的信息之前,你首先需要獲取類的 Class 對象哑姚。Java 中的所有類型包括基本類型祭饭,即使是數(shù)組都有與之關聯(lián)的 Class 類的對象。如果你在編譯期知道一個類的名字的話叙量,那么你可以使用如下的方式獲取一個類的 Class 對象倡蝙。

Class myObjectClass = MyObject.class;

如果你已經(jīng)得到了某個對象,但是你想獲取這個對象的 Class 對象绞佩,那么你可以通過下面的方法得到:

Student me = new Student("mr.simple");

Class clazz = me.getClass();

如果你在編譯期獲取不到目標類型寺鸥,但是你知道它的完整類路徑,那么你可以通過如下的形式來獲取 Class 對象:

Class myObjectClass = Class.forName("com.simple.User");

在使用 Class.forName()方法時征炼,你必須提供一個類的全名析既,這個全名包括類所在的包的名字。例如 User 類位于 com.simple 包谆奥,那么他的完整類路徑就是 com.simple.User眼坏。

如果在調(diào)用 Class.forName()方法時,沒有在編譯路徑下(classpath)找到對應的類酸些,那么將會拋出 ClassNotFoundException宰译。

接口說明

// 加載指定的 Class 對象,參數(shù) 1 為要加載的類的完整路徑魄懂,例如"com.simple.Student". ( 常用方式 )

public static Class forName (String className)

// 加載指定的 Class 對象沿侈,參數(shù) 1 為要加載的類的完整路徑,例如"com.simple.Student";

// 參數(shù) 2 為是否要初始化該 Class 對象市栗,參數(shù) 3 為指定加載該類的 ClassLoader.

public static Class forName (String className, boolean shouldInitialize, ClassLoader classLoader)

2.2 通過 Class 對象構(gòu)造目標類型的對象

一旦你拿到 Class 對象之后缀拭,你就可以為所欲為了咳短!當你善用它的時候它就是神兵利器,當你心懷鬼胎之時它就會變成惡魔蛛淋。但獲取 Class 對象只是第一步咙好,我們需要在執(zhí)行那些強大的行為之前通過 Class 對象構(gòu)造出該類型的對象,然后才能通過該對象釋放它的能量褐荷。 我們知道勾效,在 java 中要構(gòu)造對象,必須通過該類的構(gòu)造函數(shù)叛甫,那么其實反射也是一樣一樣的层宫。但是它們確實有區(qū)別的,通過反射構(gòu)造對象其监,我們首先要獲取類的 Constructor(構(gòu)造器)對象萌腿,然后通過 Constructor 來創(chuàng)建目標類的對象。還是直接上代碼的棠赛。

private static void classForName() {

try {

// 獲取 Class 對象

Class clz = Class.forName("org.java.advance.reflect.Student");

// 通過 Class 對象獲取 Constructor哮奇,Student 的構(gòu)造函數(shù)有一個字符串參數(shù)

// 因此這里需要傳遞參數(shù)的類型 ( Student 類見后面的代碼 )

Constructor constructor = clz.getConstructor(String.class);

// 通過 Constructor 來創(chuàng)建 Student 對象

Object obj = constructor.newInstance("mr.simple");

System.out.println(" obj :? " + obj.toString());

} catch (Exception e) {

e.printStackTrace();

}

}

通過上述代碼,我們就可以在運行時通過完整的類名來構(gòu)建對象睛约。

獲取構(gòu)造函數(shù)接口

// 獲取一個公有的構(gòu)造函數(shù),參數(shù)為可變參數(shù)哲身,如果構(gòu)造函數(shù)有參數(shù)辩涝,那么需要將參數(shù)的類型傳遞給 getConstructor 方法

public Constructor getConstructor (Class... parameterTypes)

// 獲取目標類所有的公有構(gòu)造函數(shù)

public Constructor[] getConstructors ()

注意,當你通過反射獲取到 Constructor勘天、Method怔揩、Field 后,在反射調(diào)用之前將此對象的 accessible 標志設置為 true脯丝,以此來提升反射速度商膊。值為 true 則指示反射的對象在使用時應該取消 Java 語言訪問檢查。值為 false 則指示反射的對象應該實施 Java 語言訪問檢查宠进。例如 :

Constructor constructor = clz.getConstructor(String.class);

// 設置 Constructor 的 Accessible

constructor.setAccessible(true);

// 設置 Methohd 的 Accessible

Method learnMethod = Student.class.getMethod("learn"晕拆, String.class);

learnMethod.setAccessible(true);

由于后面還會用到 Student 以及相關的類,我們在這里就先給出它們的代碼吧材蹬。

Person.java

public class Person {

String mName;

public Person(String aName) {

mName = aName;

}

private void sayHello(String friendName) {

System.out.println(mName + " say hello to " + friendName);

}

protected void showMyName() {

System.out.println("My name is " + mName);

}

public void breathe() {

System.out.println(" take breathe ");

}

}

Student.java

public class Student extends Person implements Examination {

// 年級

int mGrade;

public Student(String aName) {

super(aName);

}

public Student(int grade, String aName) {

super(aName);

mGrade = grade;

}

private void learn(String course) {

System.out.println(mName + " learn " + course);

}

public void takeAnExamination() {

System.out.println(" takeAnExamination ");

}

public String toString() {

return " Student :? " + mName;

}

Breathe.java

// 呼吸接口

public interface Breathe {

public void breathe();

}

Examination.java

// 考試接口

public interface Examination {

public void takeAnExamination();

}

3 反射獲取類中函數(shù)

3.1 獲取當前類中定義的方法

要獲取當前類中定義的所有方法可以通過 Class 中的 getDeclaredMethods 函數(shù)实幕,它會獲取到當前類中的 public、default堤器、protected昆庇、private 的所有方法。而 getDeclaredMethod(String name, Class... parameterTypes)則是獲取某個指定的方法闸溃。代碼示例如下 :

private static void showDeclaredMethods() {

Student student = new Student("mr.simple");

Method[] methods = student.getClass().getDeclaredMethods();

for (Method method : methods) {

System.out.println("declared method name : " + method.getName());

}

try {

Method learnMethod = student.getClass().getDeclaredMethod("learn", String.class);

// 獲取方法的參數(shù)類型列表

Class[] paramClasses = learnMethod.getParameterTypes() ;

for (Class class1 : paramClasses) {

System.out.println("learn 方法的參數(shù)類型 : " + class1.getName());

}

// 是否是 private 函數(shù)整吆,屬性是否是 private 也可以使用這種方式判斷

System.out.println(learnMethod.getName() + " is private "

+ Modifier.isPrivate(learnMethod.getModifiers()));

learnMethod.invoke(student, "java ---> ");

} catch (Exception e) {

e.printStackTrace();

}

}

3.2 獲取當前類拱撵、父類中定義的公有方法

要獲取當前類以及父類中的所有 public 方法可以通過 Class 中的 getMethods 函數(shù),而 getMethod 則是獲取某個指定的方法表蝙。代碼示例如下 :

private static void showMethods() {

Student student = new Student("mr.simple");

// 獲取所有方法

Method[] methods = student.getClass().getMethods();

for (Method method : methods) {

System.out.println("method name : " + method.getName());

}

try {

// 通過 getMethod 只能獲取公有方法裕膀,如果獲取私有方法則會拋出異常,比如這里就會拋異常

Method learnMethod = student.getClass().getMethod("learn", String.class);

// 是否是 private 函數(shù)勇哗,屬性是否是 private 也可以使用這種方式判斷

System.out.println(learnMethod.getName() + " is private " + Modifier.isPrivate(learnMethod.getModifiers()));

// 調(diào)用 learn 函數(shù)

learnMethod.invoke(student, "java");

} catch (Exception e) {

e.printStackTrace();

}

}

接口說明

// 獲取 Class 對象中指定函數(shù)名和參數(shù)的函數(shù)昼扛,參數(shù)一為函數(shù)名,參數(shù) 2 為參數(shù)類型列表

public Method getDeclaredMethod (String name, Class... parameterTypes)

// 獲取該 Class 對象中的所有函數(shù)( 不包含從父類繼承的函數(shù) )

public Method[] getDeclaredMethods ()

// 獲取指定的 Class 對象中的**公有**函數(shù)欲诺,參數(shù)一為函數(shù)名抄谐,參數(shù) 2 為參數(shù)類型列表

public Method getMethod (String name, Class... parameterTypes)

// 獲取該 Class 對象中的所有**公有**函數(shù) ( 包含從父類和接口類集成下來的函數(shù) )

public Method[] getMethods ()

這里需要注意的是 getDeclaredMethod 和 getDeclaredMethods 包含 private、protected扰法、default蛹含、public 的函數(shù),并且通過這兩個函數(shù)獲取到的只是在自身中定義的函數(shù)塞颁,從父類中集成的函數(shù)不能夠獲取到浦箱。而 getMethod 和 getMethods 只包含 public 函數(shù),父類中的公有函數(shù)也能夠獲取到祠锣。

4 反射獲取類中的屬性

獲取屬性和章節(jié) 3 中獲取方法是非常相似的酷窥,只是從 getMethod 函數(shù)換成了 getField,從 getDeclaredMethod 換成了 getDeclaredField 罷了伴网。

4.1 獲取當前類中定義的屬性

要獲取當前類中定義的所有屬性可以通過 Class 中的 getDeclaredFields 函數(shù)蓬推,它會獲取到當前類中的 public、default澡腾、protected沸伏、private 的所有屬性。而 getDeclaredField 則是獲取某個指定的屬性动分。代碼示例如下 :

private static void showDeclaredFields() {

Student student = new Student("mr.simple");

// 獲取當前類和父類的所有公有屬性

Field[] publicFields = student.getClass().getDeclaredFields();

for (Field field : publicFields) {

System.out.println("declared field name : " + field.getName());

}

try {

// 獲取當前類和父類的某個公有屬性

Field gradeField = student.getClass().getDeclaredField("mGrade");

// 獲取屬性值

System.out.println(" my grade is : " + gradeField.getInt(student));

// 設置屬性值

gradeField.set(student, 10);

System.out.println(" my grade is : " + gradeField.getInt(student));

} catch (Exception e) {

e.printStackTrace();

}

}

4.2 獲取當前類毅糟、父類中定義的公有屬性

要獲取當前類以及父類中的所有 public 屬性可以通過 Class 中的 getFields 函數(shù),而 getField 則是獲取某個指定的屬性澜公。代碼示例如下 :

private static void showFields() {

Student student = new Student("mr.simple");

// 獲取當前類和父類的所有公有屬性

Field[] publicFields = student.getClass().getFields();

for (Field field : publicFields) {

System.out.println("field name : " + field.getName());

}

try {

// 獲取當前類和父類的某個公有屬性

Field ageField = student.getClass().getField("mAge");

System.out.println(" age is : " + ageField.getInt(student));

} catch (Exception e) {

e.printStackTrace();

}

}

接口說明

// 獲取 Class 對象中指定屬性名的屬性姆另,參數(shù)一為屬性名

public Method getDeclaredField (String name)

// 獲取該 Class 對象中的所有屬性( 不包含從父類繼承的屬性 )

public Method[] getDeclaredFields ()

// 獲取指定的 Class 對象中的**公有**屬性,參數(shù)一為屬性名

public Method getField (String name)

// 獲取該 Class 對象中的所有**公有**屬性 ( 包含從父類和接口類集成下來的公有屬性 )

public Method[] getFields ()

這里需要注意的是 getDeclaredField 和 getDeclaredFields 包含 private玛瘸、protected蜕青、default、public 的屬性糊渊,并且通過這兩個函數(shù)獲取到的只是在自身中定義的屬性右核,從父類中集成的屬性不能夠獲取到。而 getField 和 getFields 只包含 public 屬性渺绒,父類中的公有屬性也能夠獲取到贺喝。

5 反射獲取父類與接口

5.1 獲取父類

獲取 Class 對象的父類菱鸥。

Student student = new Student("mr.simple");

Class superClass = student.getClass().getSuperclass();

while (superClass != null) {

System.out.println("Student's super class is : " + superClass.getName());

// 再獲取父類的上一層父類,直到最后的 Object 類躏鱼,Object 的父類為 null

superClass = superClass.getSuperclass();

}

5.2 獲取接口

獲取 Class 對象中實現(xiàn)的接口氮采。

private static void showInterfaces() {

Student student = new Student("mr.simple");

Class[] interfaceses = student.getClass().getInterfaces();

for (Class class1 : interfaceses) {

System.out.println("Student's interface is : " + class1.getName());

}

}

6 獲取注解信息

在框架開發(fā)中,注解加反射的組合使用是最為常見形式的染苛。關于注解方面的知識請參考公共技術點之 Java 注解 Annotation鹊漠,定義注解時我們會通過@Target 指定該注解能夠作用的類型,看如下示例:

@Target({

ElementType.METHOD, ElementType.FIELD, ElementType.TYPE

})

@Retention(RetentionPolicy.RUNTIME)

static @interface Test {

}

上述注解的@target 表示該注解只能用在函數(shù)上茶行,還有 Type躯概、Field、PARAMETER 等類型畔师,可以參考上述給出的參考資料娶靡。通過反射 api 我們也能夠獲取一個 Class 對象獲取類型、屬性看锉、函數(shù)等相關的對象姿锭,通過這些對象的 getAnnotation 接口獲取到對應的注解信息。 首先我們需要在目標對象上添加上注解伯铣,例如 :

@Test(tag = "Student class Test Annoatation")

public class Student extends Person implements Examination {

// 年級

@Test(tag = "mGrade Test Annotation ")

int mGrade;

// ......

}

然后通過相關的注解函數(shù)得到注解信息呻此,如下所示 :

private static void getAnnotationInfos() {

Student student = new Student("mr.simple");

Test classTest = student.getClass().getAnnotation(Test.class);

System.out.println("class Annotatation tag = " + classTest.tag());

Field field = null;

try {

field = student.getClass().getDeclaredField("mGrade");

Test testAnnotation = field.getAnnotation(Test.class);

System.out.println("屬性的 Test 注解 tag : " + testAnnotation.tag());

} catch (Exception e) {

e.printStackTrace();

}

}

輸出結(jié)果為 : >

class Annotatation tag = Student class Test Annoatation

屬性的 Test 注解 tag : mGrade Test Annotation

接口說明

// 獲取指定類型的注解

public A getAnnotation(Class annotationClass) ;

// 獲取 Class 對象中的所有注解

public Annotation[] getAnnotations() ;

雜談

反射作為 Java 語言的重要特性,在開發(fā)中有著極為重要的作用懂傀。很多開發(fā)框架都是基于反射來實現(xiàn)對目標對象的操作趾诗,而反射配合注解更是設計開發(fā)框架的主流選擇,例如 ActiveAndroid蹬蚁,因此深入了解反射的作用以及使用對于日后開發(fā)和學習必定大有益處。


http://p.codekk.com/blogs/detail/5596953ed6459ae7934997c5

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郑兴,一起剝皮案震驚了整個濱河市犀斋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌情连,老刑警劉巖叽粹,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異却舀,居然都是意外死亡虫几,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門挽拔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辆脸,“玉大人,你說我怎么就攤上這事螃诅》惹猓” “怎么了状囱?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長倘是。 經(jīng)常有香客問我亭枷,道長,這世上最難降的妖魔是什么搀崭? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任叨粘,我火速辦了婚禮,結(jié)果婚禮上瘤睹,老公的妹妹穿的比我還像新娘升敲。我一直安慰自己,他們只是感情好默蚌,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布冻晤。 她就那樣靜靜地躺著,像睡著了一般绸吸。 火紅的嫁衣襯著肌膚如雪鼻弧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天锦茁,我揣著相機與錄音攘轩,去河邊找鬼。 笑死码俩,一個胖子當著我的面吹牛度帮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播稿存,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼笨篷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瓣履?” 一聲冷哼從身側(cè)響起率翅,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袖迎,沒想到半個月后冕臭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡燕锥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年辜贵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片归形。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡托慨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出连霉,到底是詐尸還是另有隱情榴芳,我是刑警寧澤嗡靡,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站窟感,受9級特大地震影響讨彼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柿祈,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一哈误、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躏嚎,春花似錦蜜自、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虚茶,卻和暖如春戈鲁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嘹叫。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工婆殿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人罩扇。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓婆芦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喂饥。 傳聞我的和親對象是個殘疾皇子消约,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 項目:,分析者:Mr.Simple员帮,校對者:Trinea本文為 Android 開源項目源碼解析 公共技術點中的 ...
    zizi192閱讀 364評論 0 1
  • 1. Java基礎部分 基礎部分的順序:基本語法荆陆,類相關的語法,內(nèi)部類的語法集侯,繼承相關的語法,異常的語法帜消,線程的語...
    子非魚_t_閱讀 31,581評論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理棠枉,服務發(fā)現(xiàn),斷路器泡挺,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉辈讶,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評論 0 9
  • 前言: 這是一個關于西游前世今生的故事,前生有個齊天大圣今生有個孫悟空娄猫;前生有個江流兒今生有個唐玄奘贱除。當過去與今生...
    陌簡塵閱讀 1,022評論 3 12