「轉(zhuǎn)載」Java 反射 Reflection

一、了解 Java 中的反射

1. 什么是 Java 的反射

Java反射是在程序運(yùn)行時獲取類的函數(shù)基茵、屬性、父類和接口等class內(nèi)部信息的機(jī)制

通過反射還可以讓我們在運(yùn)行期實(shí)例化對象恳谎,調(diào)用方法,通過調(diào)用 get/set 方法獲取變量的值憋肖,即使方法或?qū)傩允撬接械牡囊部梢酝ㄟ^反射的形式調(diào)用惠爽,這種“看透 class”的能力被稱為內(nèi)省,這種能力在框架開發(fā)中尤為重要瞬哼。

有些情況下,我們要使用的類在運(yùn)行時才會確定租副,這個時候我們不能在編譯期就使用它坐慰,因此只能通過反射的形式來使用在運(yùn)行時才存在的類(該類符合某種特定的規(guī)范,例如 JDBC)用僧,這是反射用得比較多的場景结胀。

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

2. Class 類

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

當(dāng)我們編寫完一個 Java 項(xiàng)目之后剥汤,所有的 Java 文件都會被編譯成一個.class 文件颠放,這些 Class 對象承載了這個類型的父類、接口吭敢、構(gòu)造函數(shù)碰凶、方法、屬性等原始信息鹿驼,這些 class 文件在程序運(yùn)行時會被 ClassLoader 加載到虛擬機(jī)中欲低。當(dāng)一個類被加載以后,Java 虛擬機(jī)就會在內(nèi)存中自動產(chǎn)生一個 Class 對象畜晰。我們通過 new 的形式創(chuàng)建對象實(shí)際上就是通過這些 Class 來創(chuàng)建砾莱,只是這個過程對于我們是不透明的而已。下面的章節(jié)中我們會為大家演示反射的一些常用 api舷蟀,從代碼的角度理解反射恤磷。

二、反射 Class 以及構(gòu)造對象

1. 獲取 Class 對象

在你想檢查一個類的信息之前野宜,你首先需要獲取類的 Class 對象扫步。Java 中的所有類型包括基本類型,即使是數(shù)組都有與之關(guān)聯(lián)的 Class 類的對象匈子。如果你在編譯期知道一個類的名字的話河胎,那么你可以使用如下的方式獲取一個類的 Class 對象。

Class<?> myObjectClass = MyObject.class;

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

Student me = new Student("mr.simple");
Class<?> clazz = me.getClass();

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

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

在使用 Class.forName()方法時胚迫,你必須提供一個類的全名,這個全名包括類所在的包的名字唾那。例如 User 類位于 com.simple 包访锻,那么他的完整類路徑就是 com.simple.User。
如果在調(diào)用 Class.forName()方法時闹获,沒有在編譯路徑下(classpath)找到對應(yīng)的類期犬,那么將會拋出 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.通過 Class 對象構(gòu)造目標(biāo)類型的對象

一旦你拿到 Class 對象之后佳吞,你就可以為所欲為了!當(dāng)你善用它的時候它就是神兵利器旭斥,當(dāng)你心懷鬼胎之時它就會變成惡魔容达。但獲取 Class 對象只是第一步,我們需要在執(zhí)行那些強(qiáng)大的行為之前通過 Class 對象構(gòu)造出該類型的對象垂券,然后才能通過該對象釋放它的能量花盐。 我們知道,在 java 中要構(gòu)造對象菇爪,必須通過該類的構(gòu)造函數(shù)算芯,那么其實(shí)反射也是一樣一樣的。但是它們確實(shí)有區(qū)別的凳宙,通過反射構(gòu)造對象熙揍,我們首先要獲取類的 Constructor(構(gòu)造器)對象,然后通過 Constructor 來創(chuàng)建目標(biāo)類的對象氏涩。還是直接上代碼的届囚。

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();
        }
    }

通過上述代碼是尖,我們就可以在運(yùn)行時通過完整的類名來構(gòu)建對象意系。

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

// 獲取一個公有的構(gòu)造函數(shù),參數(shù)為可變參數(shù)饺汹,如果構(gòu)造函數(shù)有參數(shù)蛔添,那么需要將參數(shù)的類型傳遞給 getConstructor 方法
public Constructor<T> getConstructor (Class...<?> parameterTypes)
// 獲取目標(biāo)類所有的公有構(gòu)造函數(shù)
public Constructor[]<?> getConstructors ()

注意,當(dāng)你通過反射獲取到 Constructor兜辞、Method迎瞧、Field 后,在反射調(diào)用之前將此對象的 accessible 標(biāo)志設(shè)置為 true逸吵,以此來提升反射速度凶硅。值為 true 則指示反射的對象在使用時應(yīng)該取消 Java 語言訪問檢查。值為 false 則指示反射的對象應(yīng)該實(shí)施 Java 語言訪問檢查扫皱。例如 :

Constructor<?> constructor = clz.getConstructor(String.class);
   // 設(shè)置 Constructor 的 Accessible
   constructor.setAccessible(true);

   // 設(shè)置 Methohd 的 Accessible
   Method learnMethod = Student.class.getMethod("learn"咏尝, String.class);
   learnMethod.setAccessible(true);

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();
}

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

1. 獲取當(dāng)前類中定義的方法

要獲取當(dāng)前類中定義的所有方法可以通過 Class 中的 getDeclaredMethods 函數(shù)啸罢,它會獲取到當(dāng)前類中的 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();
        }
    }

2. 獲取當(dāng)前類蕾总、父類中定義的公有方法

要獲取當(dāng)前類以及父類中的所有 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ù)也能夠獲取到昭躺。

四忌锯、反射獲取類中的屬性

獲取屬性和章節(jié) 三 中獲取方法是非常相似的,只是從 getMethod 函數(shù)換成了 getField窍仰,從 getDeclaredMethod 換成了 getDeclaredField 罷了汉规。

1. 獲取當(dāng)前類中定義的屬性

要獲取當(dāng)前類中定義的所有屬性可以通過 Class 中的 getDeclaredFields 函數(shù),它會獲取到當(dāng)前類中的 public驹吮、default针史、protected、private 的所有屬性碟狞。而 getDeclaredField 則是獲取某個指定的屬性啄枕。代碼示例如下 :

private static void showDeclaredFields() {
        Student student = new Student("mr.simple");
        // 獲取當(dāng)前類的所有公有屬性
        Field[] publicFields = student.getClass().getDeclaredFields();
        for (Field field : publicFields) {
            System.out.println("declared field name : " + field.getName());
        }

        try {
            // 獲取當(dāng)前類的某個公有屬性
            Field gradeField = student.getClass().getDeclaredField("mGrade");
            // 獲取屬性值
            System.out.println(" my grade is : " + gradeField.getInt(student));
            // 設(shè)置屬性值
            gradeField.set(student, 10);
            System.out.println(" my grade is : " + gradeField.getInt(student));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2. 獲取當(dāng)前類、父類中定義的公有屬性

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

private static void showFields() {
        Student student = new Student("mr.simple");
        // 獲取當(dāng)前類和父類的所有公有屬性
        Field[] publicFields = student.getClass().getFields();
        for (Field field : publicFields) {
            System.out.println("field name : " + field.getName());
        }

        try {
            // 獲取當(dāng)前類和父類的某個公有屬性
            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 ()

五常空、反射獲取父類與接口

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();
    }

2. 獲取接口

獲取 Class 對象中實(shí)現(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());
        }
    }

六烘嘱、獲取注解信息

在框架開發(fā)中昆禽,注解加反射的組合使用是最為常見形式的蝗蛙。定義注解時我們會通過@Target 指定該注解能夠作用的類型,看如下示例:

    @Target({
       ElementType.METHOD, ElementType.FIELD, ElementType.TYPE
    })
    @Retention(RetentionPolicy.RUNTIME)
       static @interface Test {

    }

上述注解的@target 表示該注解只能用在函數(shù)上醉鳖,還有 Type捡硅、Field、PARAMETER 等類型盗棵,可以參考上述給出的參考資料壮韭。通過反射 api 我們也能夠獲取一個 Class 對象獲取類型、屬性漾根、函數(shù)等相關(guān)的對象泰涂,通過這些對象的 getAnnotation 接口獲取到對應(yīng)的注解信息。 首先我們需要在目標(biāo)對象上添加上注解辐怕,例如 :

@Test(tag = "Student class Test Annoatation")
public class Student extends Person implements Examination {
    // 年級
    @Test(tag = "mGrade Test Annotation ")
    int mGrade;

    // ......
}

然后通過相關(guān)的注解函數(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 extends Annotation> A getAnnotation(Class<A> annotationClass) ;
// 獲取 Class 對象中的所有注解
public Annotation[] getAnnotations() ;

雜談
反射作為 Java 語言的重要特性,在開發(fā)中有著極為重要的作用寄疏。很多開發(fā)框架都是基于反射來實(shí)現(xiàn)對目標(biāo)對象的操作是牢,而反射配合注解更是設(shè)計開發(fā)框架的主流選擇,例如 ActiveAndroid陕截,因此深入了解反射的作用以及使用對于日后開發(fā)和學(xué)習(xí)必定大有益處驳棱。

轉(zhuǎn)自公共技術(shù)點(diǎn)之 Java 反射 Reflection

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市农曲,隨后出現(xiàn)的幾起案子社搅,更是在濱河造成了極大的恐慌,老刑警劉巖乳规,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件形葬,死亡現(xiàn)場離奇詭異,居然都是意外死亡暮的,警方通過查閱死者的電腦和手機(jī)笙以,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冻辩,“玉大人猖腕,你說我怎么就攤上這事『奚粒” “怎么了倘感?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咙咽。 經(jīng)常有香客問我侠仇,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任逻炊,我火速辦了婚禮,結(jié)果婚禮上犁享,老公的妹妹穿的比我還像新娘余素。我一直安慰自己,他們只是感情好炊昆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布桨吊。 她就那樣靜靜地躺著,像睡著了一般凤巨。 火紅的嫁衣襯著肌膚如雪视乐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天敢茁,我揣著相機(jī)與錄音佑淀,去河邊找鬼。 笑死彰檬,一個胖子當(dāng)著我的面吹牛伸刃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逢倍,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼捧颅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了较雕?” 一聲冷哼從身側(cè)響起碉哑,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亮蒋,沒想到半個月后扣典,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宛蚓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年激捏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凄吏。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡远舅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痕钢,到底是詐尸還是另有隱情图柏,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布任连,位于F島的核電站蚤吹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裁着,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一繁涂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧二驰,春花似錦扔罪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矗积,卻和暖如春全肮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棘捣。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工辜腺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柱锹。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓哪自,卻偏偏與公主長得像,于是被迫代替她去往敵國和親禁熏。 傳聞我的和親對象是個殘疾皇子壤巷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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

  • 前言 人生苦多,快來 Kotlin 瞧毙,快速學(xué)習(xí)Kotlin胧华! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,201評論 9 118
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理宙彪,服務(wù)發(fā)現(xiàn)矩动,斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 1. 了解 Java 中的反射 1.1 什么是 Java 的反射 Java 反射是可以讓我們在運(yùn)行時獲取類的函數(shù)释漆、...
    Ten_Minutes閱讀 536評論 0 4
  • `在這篇文章中悲没,我想跟你分享 20 條由CSS社區(qū)推薦的約定和最佳實(shí)踐。 有些建議可能比較適合新手男图,而有些則更高級...
    景岳閱讀 217評論 0 3
  • 在被支付寶示姿、銀聯(lián)坑過之后,發(fā)現(xiàn)其實(shí)微信支付的集成并沒有想象中的那么困難逊笆,像支付寶那樣簡單地調(diào)用個方法就行栈戳,重要的難...
    petry閱讀 703評論 2 2