一篇文章弄懂 Java 反射的使用

說到Java反射,必須先把 Java 的位元組碼搞明白了,也就是 Class , 大 Class
在之前的文章中,我們知道了Java的大Class就是類的位元組碼,就是一個普通的類,裏面保存的是類的信息,還不太明白Java的大Class的,可以先看一下之前的文章 一篇文章徹底搞懂Java的大Class到底是什麼

先想一個問題

1. 給我們一個類,我們如何使用?

這還不簡單,通過這個類,創建一個類的對象,再通過這個對象,調用類的方法或者屬性

比如有一個類叫 Student , 裏面有一個 name字段和一個 age 字段,還有3個方法, 源碼如下:

package com.model;

public class Student {
    private String name;
    private int age;

    public Student(){
    }

    public Student(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 show(){
        System.out.println("name=" + this.name + " age=" + this.age);
    }
}

上面的代碼很簡單,應該都能看懂,我們以這個Student類來實驗

回到上面的問題:如何使用這個類? ,代碼如下:

        //1 創建一個對象
        Student s = new Student();

        //2 調用對象的方法
        s.setName("李雷");
        s.setAge(23);

        //3 打印一下
        s.show();

打印的結果下:
name=李雷 age=23

上面就是和 反射 相反的通過正常的方式創建一個類的對象,然後通過對象調用類的方法

其實我們還可以根據類的位元組碼來創建對象,然後調用類的方法
也就是通過某個類的 Class ,來創建對象,然後調用類的方法

2. 如何獲取類的 Class 呢?

3個方法,以Student為例,演示如下:

第一種:通過 Class.forName(“com.model.Student”) 來獲取Student的 Class

代碼如下:

Class cls = Class.forName("com.model.Student");

第二種:通過 Student.class

Class cls = Student.class

第三種:通過類的對象來獲取,調用類的對象的 getClass()方法

Student s = new Student();
Class cls = s.getClass()

以上就是三種方法獲取一個類的 Class 的方法,必須要牢記,尤其是前 2 個,用的最多

3. 如何通過Class來創建對象,進而來調用類的方法或者屬性呢?

  • 第一步:獲取類的 Class 對象
  • 第二步:獲取對應的方法的位元組碼 Method 以及 構造函數的位元組碼 Constructor ,或者字段的位元組碼 Field
  • 第三步:通過ConstructornewInstance()方法生成一個類的對象
  • 第四步:通過調用 Methodinvoke()方法完成調用類的代碼

代碼演示如下:

        //第一步:獲取Student的 Class 對象,即Student的位元組碼
        Class cls = Class.forName("com.model.Student");

        //第二步:獲取無參的構造方法的位元組碼,當然也可以獲取有參的
        Constructor constructor = cls.getConstructor();

        //第三步:調用構造函數的位元組碼對象的 newInstance 方法創建 Student的對象 obj
        Object obj = constructor.newInstance();

        //第四步:獲取 setName 方法的位元組碼,注意參數傳方法的名字以及方法中參數的位元組碼
        //      獲取了setName的位元組碼 method,調用方法必須要有一個對象,所以上面的obj對象就是用來此處的
        //      一定要傳進行
        Method method = cls.getMethod("setName", String.class);
        method.invoke(obj,"待兔");

        //和上面類似,只不過這次 getMethod 的第二個參數傳的是 int.class
        //因為第二個參數是int類型
        Method method1 = cls.getMethod("setAge", int.class);
        method1.invoke(obj,23);

        //和上面類似 ,只不過 show()方法是無參的,所以 getMethod 只需要傳方法的名字"show" 即可
        Method showMethod = cls.getMethod("show");

        //最後:調用showMethod方法,通過調用showMethod的invoke方法,裏面傳入前面創建的obj對象
        //就達到了調用對象的show方法
        showMethod.invoke(obj);

通過上面的代碼演示可以看出,在不知道 Student 類型的情況下,我們只需要知道 Student類的全類名(包名+類名)
也就是com.model.Student ,就可以獲取到 Student類的

和直接通過 Student s = new Student(); s.show(); 這種方法不一樣的是,上面是在運行時通過字符串值知道要運行的類是com.model.Student

所以,反射就是在運行的時候 ,才知道這個類是什麼,並且可以在運行的時候 ,獲取這個類的完整信息,並調用對應的方法

4. 常用的反射API

4.1 在反射中,要獲取一個類或調用一個類的方法,我們首先需要獲取到該類的 Class 對象

獲取Class對象有三種方法,上面已經講過,這裡再次貼出來,加深印象

  • 使用 Class.forName 靜態方法,前提是你知道類的全類名
    Class cls = Class.forName("com.model.Student"); ,其實這種方法就是加載類的
  • 使用類的 .class 方法
    Class cls = Student.class
    不過這種方法,只適合在編譯時就知道操作的 Class
  • 使用類對象的 getClass() 方法。
    Student s = new Student();
    Class cls = s.getClass()

4.2 獲取所有類的的方法

可以通過 Class對象 getMethods()或者 cls.getDeclaredMethods() 來獲取所有的方法的位元組碼

兩者的區別是:getMethods()獲取的方法包括父類的,getDeclaredMethods() 獲取的是子類的

演示 getMethods()

Method[] methods = cls.getMethods();
for (Method m : methods) {
    System.out.println(m.getName());
}

輸出出下:

getName
setName
setAge
show
getAge
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

可以看到,輸出了很多父類中的方法(Object類中的方法)

再來看一下 getDeclaredMethods() 方法

Method[] methods = cls.getDeclaredMethods();
for (Method m : methods) {
    System.out.println(m.getName());
}

輸出如下:

getName
setName
setAge
show
getAge

可以看到,只有子類自己的方法,並沒有父類的方法

5. 通過反射創建類的對象需要注意的點

上面我們通過 Constructor 對象的newInstance()方法,來創建對象
其實還有一種方法,也可以使用 Class對象的newInstance()

5.1 第一種:通過 Class 對象 newInstance()方法

Class cls = Class.forName("com.model.Student");
Student obj = (Student) cls.newInstance();

5.2 第二種:通過 Constructor 對象的 newInstance() 方法

//第一步:獲取Student的 Class 對象,即Student的位元組碼
Class cls = Class.forName("com.model.Student");

//第二步:獲取無參的構造方法的位元組碼,當然也可以獲取有參的
Constructor constructor = cls.getConstructor();

//第三步:調用構造函數的位元組碼對象的 newInstance 方法創建 Student的對象 obj
Object obj = constructor.newInstance();

::: warning
通過 Constructor 對象創建類對象可以選擇特定構造方法,而通過 Class 對象則只能使用默認的無參數構造方法。
下面的代碼就調用了一個有參數的構造方法進行了類對象的初始化。
:::

Class cls = Class.forName("com.model.Student");
Constructor constructor = cls.getConstructor(String.class,int.class);
Object obj = constructor.newInstance("tom",23);

通過上面的講解,應該對反射的用法有了個大致的了解了,Class有很多方法,感興趣的可以自己寫個helloworld調試一下
不過怎麼說,還是要先弄明白 Class 到底是什麼,知道了 Class 的本質 ,再來看反射,就很容易了

還不太明白的一定要看看下面的文章
一篇文章徹底搞懂Java的大Class到底是什麼