一文理解反射機制—基礎

概述

爲什麼需要反射

在解釋反射前先認識一下java的靜態語言是什麼。

動態語言

是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構。
主要動態語言:Object-C、C#、JavaScript、PHP、Python、Erlang。

靜態語言

與動態語言相對應的,運行時結構不可變的語言就是靜態語言。
如:Java、C、C++。

java是靜態語言??那爲什麼java還能夠如此靈活受歡迎呢?java雖然不是動態語言,但是它可以成爲“準動態語言”,也就是說java有一定的動態性,即可以利用反射機制、字節碼操作獲得類似動態語言的特性。java的動態性讓編程的時候更加靈活了!

反射是什麼

Reflection(反射)是被視爲動態語言的關鍵,反射機制允許程序在執行期間藉助於Reflection API取得任何類的內部信息,並且能夠直接操作任意對象的內部屬性及方法。
那反射是如何實現的呢?在JVM層面,在加載完類之後,在堆內存的方法區中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就可以包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,投過這個鏡子看到類的結構,所以,我們形容的稱之爲:反射。

示例

在靜態語言中,使用一個變量時,必須知道他的類型。在java中,變量的類型信息在編譯時都保存到了class文件中,這樣在運行時才能保證準確無誤;換句話說,程序在運行時的行爲都是固定的,如果想在運行時改變,使用反射就可以滿足。
在Spring中,有這樣的java bean配置:

<bean id="someID" class="net.liujiacai.Foobar">
    <property name="someField" value="someValue" />
</bean>

spring在處理這個bean標籤時,發現class屬性指定的是net.liujiacai.Foobar這個類,就會調用Class.forName(String)來實例化這個類,再通過反射,可以取到someField屬性的值了。
如果我們想改變這個程序運行時的信息,我們這裏直接修改bean,property的屬性即可,無需重新編譯。

在動態語言中,使用變量不需要聲明類型,因而不需要這反射這種機制。
比如在javascript中,我們知道有個變量foobar,不管foobar有沒有sayHello()屬性,我們都可以這麼寫:

foobar.sayHello()

因爲沒有類型檢查,這裏這麼寫是允許的。至於在運行時報不報錯,就要看運行時foobar的真正值了。

反射的實現--API

java反射提供的功能

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷一個類所具有的成員變量和方法
  • 在運行時獲取泛型信息
  • 在運行時條用任意一個對象的成員變量和方法
  • 在運行時處理註解
  • 生成動態代理

反射是可以得到任何他想要的類的信息啊,下面具體如何通過Reflection API來實現上述的功能。

獲取Class的實例

首先定義一個Person類:

public class Person {

    private String name;
    public int age;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(String name, int age) {

        this.name = name;
        this.age = age;
    }

    private Person(String name) {
        this.name = name;
    }

    public Person() {
        System.out.println("Person()");
    }

    public void show(){
        System.out.println("你好,我是一個人");
    }

    private String showNation(String nation){
        System.out.println("我的國籍是:" + nation);
        return nation;
    }
}

獲取Class類的實例的四種方式:

 @Test
    public void test3() throws ClassNotFoundException {
        //方式一:調用運行時類的屬性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通過運行時類的對象,調用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

        //方式三:調用Class的靜態方法:forName(String classPath)
        Class clazz3 = Class.forName("com.rxs.java.Person");
//        clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);

        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);

        //方式四:使用類的加載器:ClassLoader 
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.rxs.java.Person");
        System.out.println(clazz4);

        System.out.println(clazz1 == clazz4);

    }
class com.rxs.java.Person
Person()
class com.rxs.java.Person
class com.rxs.java.Person
true
true
class com.rxs.java.Person
true

獲取ClassLoader

實踐類加載機制中的過程。

  @Test
    public void test1(){
        //對於自定義類,使用系統類加載器進行加載
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);
        //調用系統類加載器的getParent():獲取擴展類加載器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);
        //調用擴展類加載器的getParent():無法獲取引導類加載器
        //引導類加載器主要負責加載java的核心類庫,無法加載自定義類的。
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);

        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);

    }
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@a09ee92
null
null

創建運行時的類的對象

newInstance():調用此方法,創建對應的運行時類的對象。內部調用了運行時類的空參的構造器。

要想此方法正常的創建運行時類的對象,要求:
1.運行時類必須提供空參的構造器
2.空參的構造器的訪問權限得夠。通常,設置爲public。

在javabean中要求提供一個public的空參構造器。原因:
1.便於通過反射,創建運行時類的對象
2.便於子類繼承此運行時類時,默認調用super()時,保證父類有此構造器

    @Test
    public void test1() throws IllegalAccessException, InstantiationException {

        Class<Person> clazz = Person.class;
        Person obj = clazz.newInstance();
        System.out.println(obj);

    }
Person()
Person{name='null', age=0}

獲取類的屬性信息

獲取類中屬性的權限修飾符、數據類型、變量名:

   @Test
    public void test2(){
        Class clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            //1.權限修飾符
            int modifier = f.getModifiers();
            System.out.print(Modifier.toString(modifier) + "\t");

            //2.數據類型
            Class type = f.getType();
            System.out.print(type.getName() + "\t");

            //3.變量名
            String fName = f.getName();
            System.out.print(fName);

            System.out.println();
        }


    }
private	java.lang.String	name
public	int	age

獲取類中的方法信息

獲取類中方法的權限修飾符 返回值類型 方法名(參數類型1 形參名1,…) :

 @Test
    public void test2(){
        Class clazz = Person.class;
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m : declaredMethods){
            //1.獲取方法聲明的註解
            Annotation[] annos = m.getAnnotations();
            for(Annotation a : annos){
                System.out.println(a);
            }

            //2.權限修飾符
            System.out.print(Modifier.toString(m.getModifiers()) + "\t");

            //3.返回值類型
            System.out.print(m.getReturnType().getName() + "\t");

            //4.方法名
            System.out.print(m.getName());
            System.out.print("(");
            //5.形參列表
            Class[] parameterTypes = m.getParameterTypes();
            if(!(parameterTypes == null && parameterTypes.length == 0)){
                for(int i = 0;i < parameterTypes.length;i++){

                    if(i == parameterTypes.length - 1){
                        System.out.print(parameterTypes[i].getName() + " args_" + i);
                        break;
                    }

                    System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
                }
            }

            System.out.print(")");

            //6.拋出的異常
            Class[] exceptionTypes = m.getExceptionTypes();
            if(exceptionTypes.length > 0){
                System.out.print("throws ");
                for(int i = 0;i < exceptionTypes.length;i++){
                    if(i == exceptionTypes.length - 1){
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }

                    System.out.print(exceptionTypes[i].getName() + ",");
                }
            }


            System.out.println();
        }



    }
public	java.lang.String	toString()
public	java.lang.String	getName()
public	void	setName(java.lang.String args_0)
public	void	setAge(int args_0)
public	int	getAge()
public	void	show()
private	java.lang.String	showNation(java.lang.String args_0)

調用運行時類中的指定的構造器

 @Test
    public void testConstructor() throws Exception {
        Class clazz = Person.class;

        //private Person(String name)
        /*
        1.獲取指定的構造器
        getDeclaredConstructor():參數:指明構造器的參數列表
         */

        Constructor constructor = clazz.getDeclaredConstructor(String.class);

        //2.保證此構造器是可訪問的
        constructor.setAccessible(true);

        //3.調用此構造器創建運行時類的對象
        Person per = (Person) constructor.newInstance("Tom");
        System.out.println(per);

    }
Person{name='Tom', age=0}

擴展

在這裏插入圖片描述
這些都是Class類常用的方法,在此就不一一展開了。正是因爲反射機制提供的這些API,使得能夠載運行時實現動態的效果,提高java程序的靈活性,在學習源碼時反射總是能巧妙並且簡單的使程序更加優美,學無止境,一起進步!

感謝大神們的分享:
https://segmentfault.com/q/1010000002583151
https://www.cnblogs.com/onlywujun/p/3519037.html
https://www.zhihu.com/question/28570203

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