說到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到底是什麼