吊打名企面試官 | 如何理解java的反射機制?

系列文章:

吊打名企面試官 | 當我們打開一個網頁時,究竟發生了什麼?

吊打名企面試官 | 如何理解java的反射機制?

概述

Java的反射(reflection)機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性,既然能拿到,那麼我們就可以修改部分類型信息;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射(reflection)機制。

面試問題1:什麼是反射?

面試問題2:反射相關的幾個類型分別是什麼?

面試問題3:反射的優缺點是什麼?

面試問題4:你知道的哪些框架當中用到了反射機制?

 

 

一、用途

 

 

1、在日常的第三方應用開發過程中,經常會遇到某個類的某個成員變量、方法或是屬性是私有的或是隻對系統應 用開放,這時候就可以利用Java的反射機制通過反射來獲取所需的私有成員或是方法 。

 

2、反射最重要的用途就是開發各種通用框架,比如在spring中,我們將所有的類Bean交給spring容器管理,無論 是XML配置Bean還是註解配置,當我們從容器中獲取Bean來依賴注入時,容器會讀取配置,而配置中給的就是類 的信息,spring根據這些信息,需要創建那些Bean,spring就動態的創建這些類。

 

 

二、反射相關的類

 

 

類名 用途
Class類 代表類的實體,在運行的Java應用程序中表示類和接口
Field類 代表類的成員變量/類的屬性
Method類 代表類的方法
Constructor類 代表類的構造方法

這幾個類是反射相關的重要的幾個類,童鞋們,要記住喲!

 

  • Class類(反射的入口 )

Class幫助文檔:

https://developer.android.google.cn/reference/java/lang/Class

(複製鏈接到瀏覽器查看喲)

 

Class代表類的實體,在運行的Java應用程序中表示類和接口 。

 

Java文件被編譯後,生成了.class文件,JVM此時就要去解讀.class文件 ,被編譯後的Java文件.class也被JVM解析爲一個對象,這個對象就是java.lang.Class .這樣當程序在運行時,每個java文件就最終變成了Class類對象的一個實例(老鐵們要注意,這個Class對象存放在方法區,不在堆裏面的)。我們通過Java的反射機制應用到這個實例,就可以去獲得甚至去添加改變這個類的屬性和動作,使得這個類成爲一個動態的類 。

 

(重要)常用獲得類相關的方法:

方法 用途
getClassLoader() 獲得類的加載器
getDeclaredClasses() 返回一個數組,數組中包含該類中所有類和接口類的對象(包括私有的)
forName(String className) 根據類名返回類的對象
newInstance() 創建類的實例
getName() 獲得類的完整路徑名字

 

  • 如何拿到Class對象?

 

共有3種方式可以獲得Class對象: 

 

第一種:使用  Class.forName("類的全路徑名"); 靜態方法。

前提:已明確類的全路徑名。

 

第二種:使用類名.class方法。

說明:僅適合在編譯前就已經明確要操作的 Class。

 

第三種:使用類對象的 getClass()方法

 

下面我們來一次介紹,直接上代碼:

 

/** * Created with IntelliJ IDEA. * Description: * User: GAOBO * Date: 2020-02-20 * Time: 15:24 */class Student{    //私有屬性name    private String name = "bit";    //公有屬性age    public int age = 18;    //不帶參數的構造方法    public Student(){        System.out.println("Student()");    }
    private Student(String name,int age) {        this.name = name;        this.age = age;        System.out.println("Student(String,name)");    }
    private void eat(){        System.out.println("i am eat");    }
    public void sleep(){        System.out.println("i am pig");    }
    private void function(String str) {        System.out.println(str);    }
    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}
public class TestDemo {    public static void main(String[] args) {        /*        1.通過getClass獲取Class對象         */        Student s1 = new Student();        Class c1 = s1.getClass();        /*        2.直接通過 類名.class 的方式得到,該方法最爲安全可靠,程序性能更高        這說明任何一個類都有一個隱含的靜態成員變量 class         */        Class c2 = Student.class;        /*        3、通過 Class 對象的 forName() 靜態方法來獲取,用的最多,        但可能拋出 ClassNotFoundException 異常         */        Class c3 = null;        try {            //注意這裏是類的全路徑,如果有包需要加包的路徑            c3 = Class.forName("Student");        } catch (ClassNotFoundException e) {            e.printStackTrace();        }        //一個類在 JVM 中只會有一個 Class 實例,即我們對上面獲取的        //c1,c2,c3進行 equals 比較,發現都是true        System.out.println(c1.equals(c2));        System.out.println(c1.equals(c3));        System.out.println(c2.equals(c3));    }}

 

輸出結果:true   true   true

 

到這裏,我們已經完成了反射的第一步,拿到了Class對象。我們可以看到打印結果,輸出都是true.這也印證了,Class對象本身,對於每個類來說,只有一份。且,他是在方法區的。

 

 

三、反射過程中其他類及方法的介紹

 

 

我們上面講過,反射就是在運行過程當中,動態的獲取類的屬性,方法等。那麼這些屬性和方法獲取到了之後,如何接收這些類型呢?我們來介紹一下,剩餘的類型吧!

 

  • (重要)常用獲得類相關的方法

方法 用途
getClassLoader() 獲得類的加載器
getDeclaredClasses() 返回一個數組,數組中包含該類中所有類和接口類的對象(包括私有的)
forName(String className) 根據類名返回類的對象
newInstance() 創建類的實例
getName() 獲得類的完整路徑名字

 

  • (重要)常用獲得類中屬性相關的方法(以下方法返回值爲Field相關)

    Field相關拓展鏈接:

    https://developer.android.google.cn/reference/java/lang/reflect/Field

    (複製鏈接到瀏覽器查看喲)

方法 用途
getField(String name) 獲得某個公有的屬性對象
getFields() 獲得所有公有的屬性對象
getDeclaredField(String name) 獲得某個屬性對象
getDeclaredFields() 獲得所有屬性對象

 

  • (瞭解)獲得類中註解相關的方法

方法 用途
getAnnotation(Class<A> annotationClass) 返回該類中與參數類型匹配的公有註解對象
getAnnotations() 返回該類所有的公有註解對象
getDeclaredAnnotation(Class<A> annotationClass) 返回該類中與參數類型匹配的所有註解對象
getDeclaredAnnotations() 返回該類所有的註解對象

 

  • (重要)獲得類中構造器相關的方法(以下方法返回值爲Constructor相關)

    Constructor相關拓展鏈接:

    https://developer.android.google.cn/reference/java/lang/reflect/Constructor

    (複製鏈接到瀏覽器查看喲)

方法 用途
getConstructor(Class...<?> parameterTypes) 獲得該類中與參數類型匹配的公有構造方法
getConstructors() 獲得該類的所有公有構造方法
getDeclaredConstructor(Class...<?> parameterTypes) 獲得該類中與參數類型匹配的構造方法
getDeclaredConstructors() 獲得該類所有構造方法

 

  • (重要)獲得類中方法相關的方法(以下方法返回值爲Method相關)

    Method相關拓展鏈接:

    https://developer.android.google.cn/reference/java/lang/reflect/Method

    (複製鏈接到瀏覽器查看喲)

方法 用途
getMethod(String name, Class...<?> parameterTypes) 獲得該類某個公有的方法
getMethods() 獲得該類所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes) 獲得該類某個方法
getDeclaredMethods() 獲得該類所有方法

以上我們大概列舉出了很多重要的類當中的方法,具體如何使用,我們接下來看看。

 

接下來我們開始使用反射,我們依舊反射上面的Student類,把反射的邏輯寫到另外的類中,以便進行理解。

 

注意:所有和反射相關的包都在import java.lang.reflect 包下面。

 

import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;
/** * Created with IntelliJ IDEA. * Description: * User: GAOBO * Date: 2020-02-20 * Time: 16:31 */public class ReflectClassDemo {    // 創建對象    public static void reflectNewInstance() {        try {            Class<?> classStudent = Class.forName("Student");            Object objectStudent = classStudent.newInstance();            Student student = (Student) objectStudent;            System.out.println("獲得學生對象:"+student);        } catch (Exception ex) {            ex.printStackTrace();        }    }    // 反射私有的構造方法  屏蔽內容爲獲得公有的構造方法    public static void reflectPrivateConstructor() {        try {            Class<?> classStudent = Class.forName("Student");            //注意傳入對應的參數            Constructor<?> declaredConstructorStudent = classStudent.getDeclaredConstructor(String.class,int.class);            //Constructor<?> declaredConstructorStudent = classStudent.getConstructor();            //設置爲true後可修改訪問權限            declaredConstructorStudent.setAccessible(true);            Object objectStudent = declaredConstructorStudent.newInstance("高博",15);            //Object objectStudent = declaredConstructorStudent.newInstance();            Student student = (Student) objectStudent;            System.out.println("獲得私有構造哈數且修改姓名和年齡:"+student);        } catch (Exception ex) {            ex.printStackTrace();        }    }
    // 反射私有屬性    public static void reflectPrivateField() {        try {            Class<?> classStudent = Class.forName("Student");
            Field field  = classStudent.getDeclaredField("name");            field.setAccessible(true);            //可以修改該屬性的值            Object objectStudent = classStudent.newInstance();            Student student = (Student) objectStudent;
            field.set(student,"小明");
            String name = (String) field.get(student);            System.out.println("反射私有屬性修改了name:"+ name);        } catch (Exception ex) {            ex.printStackTrace();        }    }
    // 反射私有方法    public static void reflectPrivateMethod() {        try {            Class<?> classStudent = Class.forName("Student");            Method methodStudent = classStudent.getDeclaredMethod("function",String.class);            System.out.println("私有方法的方法名爲:"+methodStudent.getName());            //私有的一般都要加            methodStudent.setAccessible(true);            Object objectStudent = classStudent.newInstance();            Student student = (Student) objectStudent;            methodStudent.invoke(student,"我是給私有的function函數傳的參數");
        } catch (Exception ex) {            ex.printStackTrace();        }    }
    public static void main(String[] args) {        //reflectNewInstance();        //reflectPrivateConstructor();        //reflectPrivateField();        reflectPrivateMethod();    }}

 



四、反射優點和缺點

 

優點:

1.對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法;

2.增加程序的靈活性和擴展性,降低耦合性,提高自適應能力;

3.反射已經運用在了很多流行框架如:Struts、Hibernate、Spring 等等。

 

缺點:

1.反射會有效率問題,會降低程序的效率;

2.反射技術繞過了源代碼的技術,會帶來維護問題。反射代碼比相應的直接代碼更復雜 。

 

 

五、總結

 

 

 

好了,以上就是反射相關的一些重要知識,以及一些簡單的應用,在後期學習框架相關的時候,大家就會更好的理解反射了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章