說到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
- 第三步:通過
Constructor
的newInstance()
方法生成一個類的對象 - 第四步:通過調用
Method
的invoke()
方法完成調用類的代碼
代碼演示如下:
//第一步:獲取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到底是什么