《重學Java系列》之 反射(上)

不詩意的女程序媛不是好廚師~
轉載請註明出處,From李詩雨—https://blog.csdn.net/cjm2484836553/article/details/103350829

在這裏插入圖片描述


今天我們來學習學習反射,如果單純講反射的使用的話,其實一點也不復雜,我們只需要對照相應的API進行調用就可以了。但是要想對反射有較深層的認識和感悟,那就需要我們多看源碼。從真實的實戰中,我們才能體會其精髓。

所以,對於反射的學習我將分爲上、中、下 3篇來總結。上篇講思維導圖中的前三點:反射是什麼,Class類 和 反射的基本使用。中篇從動態代理的源碼中去體會反射,下篇從Retrofit源碼中再去體會反射。如此步步深入才能學到點東西。

今天講的是反射基礎,但是不要小瞧基礎哦,沒有基礎什麼都白搭。話不多說,就讓我們開始吧~ ♥

1.反射是什麼?

說到反射,可能大家即陌生又熟悉。爲什麼這麼說呢?
其實,反射在我們自己平時的開發過程中用到的並不多,因此它是陌生的。但是,在我們使用到的很多框架代碼中,一旦你要去閱讀他們的源碼,就會經常發現反射的身影,所以說它又是十分熟悉的。

那到底什麼是反射呢?

反射就是在運行時才知道要操作的類是什麼,並且可以在運行時獲取類的完整構造,並調用對應的方法。

這麼官方的解釋是不是聽着還有點不太明白?沒事,我再來換種方法跟大家說。

咱們先不說反射,咱們來先說 【正】

大家回憶一下,【正】常的 我們怎麼來實例化一個對象?
是不是通過 new,直接new一個對象出來,然後我就可訪問類的成員、調用類的方法了。

//實例化對象的標準用法,也就是所謂的【正】
        Person person=new Person("夏天",18);
        person.say("我是男神~");

【反】 射 就不一樣了,它是一開始並不知道我要初始化的類是什麼?既然都不知道類是什麼,那當然不能用 new 關鍵字來 正面的 直接的創建對象了。那我們該怎麼辦呢?這種情況下我們就可以使用 JDK 提供的反射 API 進行反射調用。

        //反射的使用 【反】
        Class personClass = Person.class;//要先獲取對應的Class對象
        Person reflePerson=(Person)personClass.newInstance();//通過class得到類的實例
        reflePerson.say("哈哈哈哈哈哈"); //然後纔可以對對象進行其他操作

這個時候你是不是會有疑問:
我們平時寫代碼都知道是什麼類啊,直接new個對象出來就好了。什麼時候纔會出現不知道new的是什麼類呢?

你還別說, 反射在我們平時自己代碼中還真的基本用不到,它是在我們寫框架代碼的時候纔會用到。如果是框架的開發人員,或者是在我們閱讀源碼的時候那反射就用的多了。

大家在用Retrofit網絡請求的框架的時候,有沒有想過:這個框架它在創建的時候,它是怎麼知道將來我們的調用者會創建什麼樣的類呢?他顯然是不知道的。那它怎麼來new出我們調用者所需要的對象呢?這裏我們就需要用到反射了。

所以反射的定義我這麼這個時候再看一下,是不是就明白了許多呢:
反射是在運行時才知道要操作的類是什麼,並且在運行時來獲取類的完整構造,並調用對應的方法

Reflection(反射) 是Java被視爲動態語言的關鍵,反射機制允許程序在執行期藉助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法

我們來歸納一下Java反射機制主要提供的功能

  • 運行時構造任意一個類的對象
  • 運行時獲取任意一個類所具有的成員變量和方法
  • 運行時調用任意一個對象的方法(屬性)

雖然我說的比較多,但相信經過這樣的反覆說說說之後,你看一遍就可以懂了。

大家都知道Java 是一門面向對象的語言。在面向對象的世界裏,萬事萬物皆對象。
我就又要問問大家了:既然萬事萬物皆對象,那我們寫的每一個類(比如說Person類),是不是應該也是對象呀。那它又是誰的對象呢?
答案是:它是Class類的對象。

好下面我們就來說說Class類。

2.瞭解Class類

(1). Class類是什麼?

上面我們說了,我們寫的每一個類都可以看成一個對象,是java.lang.Class 類的對象。

那麼Class到底是什麼呢?
Class它是一個類它封裝了當前的類裏面所包括的所有信息

一個類有哪些信息呢?
一個類中有屬性,方法,構造器等。
比如說有一個Person類,一個Fruit類,一個Animal類,這些都是不同的類,現在我們需要一個類,用來描述這些類,這就是Class。所以Class是用來描述類的類。

你可以這樣理解:Class類是一個對象照鏡子的結果,對象可以看到自己有哪些屬性,方法,構造器,實現了哪些接口等等。

對於每個類而言,JRE 都爲其保留一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個類的有關信息。

而且大家需要知道:一個類(而不是一個對象)在 JVM 中只會有一個Class實例。
我們可以new出這個類的很多個對象,但是這個類在JDK中只會有一個Class對象。
比如說:一個Person類,它可以new出很多個對象。

Person p1=new Person();
Person p2=new Person();
Person p3=new Person();

但是Person類對應的Class類的實例只有唯一一個。

(2). 獲取Class對象的3種方式

那麼,我們要怎麼樣才能獲取到Class對象呢?
這裏有3種方式:

  • 方式1:通過類名獲取 --> 類名.class
  • 方式2:通過對象獲取 --> 對象名.getClass()
  • 方式3:通過全類名獲取 --> Class.forName(全類名,即包名+類名)
    以Person類爲例:
        //獲取對應的Class對象(有3種方式)
        Class personClass = Person.class;//方式1:通過類名獲取  -->  類名.class
        Class personClass2 = person.getClass();//方式2:通過對象獲取 --> 對象名.getClass()
        Class personClass3=Class.forName("reflect.Person"); //方式3:通過全類名獲取 --> Class.forName(全類名)  可能會出現“ClassNotFoundException”,必須對其進行捕獲或聲明以便拋出

(3). Class類的常用方法

在這裏插入圖片描述

3.反射的基本使用

上面我們已經知道了如何拿到class對象。
但是純粹的拿到class對象,其實是沒有什麼實際含義的,我們真正的目的是爲了能夠設置它的屬性,調用它的方法。

那我們如何去獲取和修改它的屬性、調用它的方法呢?
其實像構造器,屬性,方法這些信息在Java中都有對應的類來表示。
在這裏插入圖片描述
下面就讓我們分別來看看該怎麼用:

首先我們定義一個Person類:

/**
 * @author shiyu
 * @create 2019-12-02 9:50
 */
public class Person {
    //私有字段
    private String name;
    private int age;

    //公有字段
    public String sex;

    //無參構造方法
    public Person() {
    }

    //有參構造方法
    public Person(String name, int age) {
        this.name = name;
        this.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 void say(String message){
        System.out.println(message);
    }

    //普通私有方法
    private void secret(){
        System.out.println("我是Person的私有方法");
    }
}

讓我們來提取一下Person類的信息:
Person類的思維導圖

(1).Constructor的基本使用

在這裏插入圖片描述
使用要點

  • getConstructors() :可以獲取該類的所有構造器
  • getConstructor() :可以獲取某一個指定參數的構造器,需要參數列表。
    而且需要注意 傳的參數列表必須嚴格的一樣,否則會報NoSuchMethodException。
    比如 【clazz0.getConstructor(String.class, int.class);】//如果不傳int.class,而傳Integer.class是不行的.
  • 得到構造器後,再調用構造器的 newInstance() 方法可以創建對象

直接上代碼來演示使用:

//------------------------Constructor的基本使用-----------------------
        String className0 = "reflect.Person";
        Class<Person> clazz0 = (Class<Person>) Class.forName(className0);

        //getConstructors()---獲取全部Constructor對象
        Constructor<Person>[] constructors = (Constructor<Person>[]) clazz0.getConstructors();
        for(Constructor<Person> constructor: constructors){
            System.out.println("getConstructors()獲取到的構造器:"+constructor);
        }
        System.out.println();

         //getConstructor()----獲取某一個Constructor 對象,需要參數列表
        //  //注意此處傳的參數列表必須嚴格的一樣,否則會報有可能NoSuchMethodException
        Constructor<Person> constructor = clazz0.getConstructor(String.class, int.class);//如果傳Integer.class是不行的.
        System.out.println("getConstructor()獲取到的構造器:"+constructor);
        //調用構造器的 newInstance() 方法創建對象
        Person malegod=constructor.newInstance("夏夏夏",26);
        malegod.say("我負責指導詩雨學習!");
        System.out.println();

我們來看下打印結果:
在這裏插入圖片描述

(2).Field的基本使用

在這裏插入圖片描述
使用要點:

  • getDeclaredFields():可以獲取 自己的 所有字段( 包括私有的),但不能獲取父類字段
  • getDeclaredField(name)—獲取指定字段
  • 如果獲取的字段是私有的,不管是讀還是寫,都要先 field.setAccessible(true);纔可以。否則會報:IllegalAccessException。

老規矩直接上代碼:

//-------------Field的基本使用--------------------
        Person goddess=new Person("賈玲",18);//先new個女神

        String className = "reflect.Person";
        Class clazz = Class.forName(className);

        //getDeclaredFields()---獲取 自己的 公有和私有的所有字段,但不能獲取父類字段
        Field[] fields = clazz.getDeclaredFields();
        for(Field field: fields){
            System.out.println("getDeclaredFields()獲取的字段:"+ field.getName());
        }
        System.out.println();

        //getDeclaredField()---獲取指定字段,傳入字段的名稱
        Field field=clazz.getDeclaredField("name");
        Field field2 = clazz.getDeclaredField("sex");

        //獲取的字段是私有的,不管是讀還是寫都要先設置 field.setAccessible(true);
        field.setAccessible(true);
        System.out.println("getDeclaredField()獲取的字段:"+field.getName());//field.getName()是讀操作,所以在此之前要設置field.setAccessible(true)
        System.out.println("getDeclaredField()獲取的字段:"+field2.getName());

        //"獲取指定字段的值"
        Object val = field.get(goddess);
        Object val2 = field2.get(goddess);
        System.out.println(field.getName()+"="+val);
        System.out.println(field2.getName()+"="+val2);
        //修改指定字段的爲指定的值
        field.set(goddess,"郭德綱"); //相當於是寫操作
        System.out.println(field.getName()+"="+goddess.getName());//這個時候再打印,女神的名字就變成了郭德綱了

看一下打印結果:
在這裏插入圖片描述

(3).Method的基本使用

在這裏插入圖片描述
使用要點:

  • getMethods():可以獲取clazz對應類的所有公有方法,包括從父類繼承來的方法,私有方法不能得到。
  • getDeclaredMethods():只獲取當前類的所有方法,包括私有方法
  • getDeclaredMethod():獲取指定的方法,需要參數名稱和參數列表,無參則不需要寫。
    也要注意 傳入的參數類型要嚴格一致。
  • 私有方法的執行,必須在調用invoke之前加上一句method.setAccessible(true)

上代碼:

//-------------Method的基本使用--------------------
        Person goddess=new Person("賈玲",18);//有一個女神

        String className = "reflect.Person";
        Class clazz = Class.forName(className);

        //getMethods()---獲取clazz對應類所有公有方法,包括從父類繼承來的方法
        Method[] methods= clazz.getMethods();
        for(Method method:methods){
            System.out.println("getMethods()獲取的方法: "+method.getName()+"()");
        }
        System.out.println("");

        //getDeclaredMethods()---只獲取當前類的所有方法,包括私有方法
        methods = clazz.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(" getDeclaredMethods()獲取的方法:"+method.getName()+"()");
        }
        System.out.println("");

        //getDeclaredMethod()---獲取指定的方法,需要參數名稱和參數列表,無參則不需要寫
        //注意 是 參數寫成int.class
        Method method = clazz.getDeclaredMethod("setAge", int.class);//獲取的是方法public void setAge(int age) {  }
        System.out.println("getDeclaredMethod()獲取的指定方法:"+method);
        System.out.println("");

        //第一個參數表示執行哪個對象的方法,剩下的參數是執行方法時需要傳入的參數"
        method.invoke(goddess,25);

        System.out.println("此時女神的年齡是:"+goddess.getAge());
        System.out.println();

        /*私有方法的執行,必須在調用invoke之前加上一句method.setAccessible(true);*/
        method = clazz.getDeclaredMethod("secret");
        System.out.println(method);
        method.setAccessible(true);//在訪問私有域時同樣
        method.invoke(goddess);//執行私有方法

打印結果大家可以對照Person類的思維導圖看:
在這裏插入圖片描述在這裏插入圖片描述

小結與回顧

通過上面的學習我們知道了

獲取class對象有3種方式:

  • 1:通過類名獲取 --> 類名.class
 Class personClass = Person.class;
  • 2.通過對象獲取 --> 對象名.getClass()
Class personClass2 = person.getClass()
  • 3.通過全類名獲取 --> Class.forName(全類名)
Class personClass3=Class.forName("reflect.Person"); 

那麼請問大家 獲取一個類的實例對象方法 有幾種呢?
其實是有3種的:

  • 直接new出來
Person person=new Person("夏天",18);
  • 2.獲取class對象後,調用newInstance()方法
 Class personClass = Person.class;//要先獲取對應的Class對象
 Person reflePerson=(Person)personClass.newInstance();//通過class得到類的實例

3.通過反射拿到這個類的構造器之後,再通過構造器的newInstance獲得。

 Constructor<Person> constructor = clazz0.getConstructor(String.class, int.class);
 Person malegod=constructor.newInstance("夏夏夏",26);

今天的內容其實很簡單,相信大家看了一遍之後就懂了會了,有了這些基礎之後,下次我們再看源碼就會順利很多了~

積累點滴,做好自己~

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