系列文章:
概述
Java的反射(reflection)機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性,既然能拿到,那麼我們就可以修改部分類型信息;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射(reflection)機制。
面試問題1:什麼是反射?
面試問題2:反射相關的幾個類型分別是什麼?
面試問題3:反射的優缺點是什麼?
面試問題4:你知道的哪些框架當中用到了反射機制?
一、用途
1、在日常的第三方應用開發過程中,經常會遇到某個類的某個成員變量、方法或是屬性是私有的或是隻對系統應 用開放,這時候就可以利用Java的反射機制通過反射來獲取所需的私有成員或是方法 。
2、反射最重要的用途就是開發各種通用框架,比如在spring中,我們將所有的類Bean交給spring容器管理,無論 是XML配置Bean還是註解配置,當我們從容器中獲取Bean來依賴注入時,容器會讀取配置,而配置中給的就是類 的信息,spring根據這些信息,需要創建那些Bean,spring就動態的創建這些類。
二、反射相關的類
類名 | 用途 |
---|---|
Class類 | 代表類的實體,在運行的Java應用程序中表示類和接口 |
Field類 | 代表類的成員變量/類的屬性 |
Method類 | 代表類的方法 |
Constructor類 | 代表類的構造方法 |
這幾個類是反射相關的重要的幾個類,童鞋們,要記住喲!
-
Class類(反射的入口 )
Class幫助文檔:
https://developer.android.google.cn/reference/java/lang/Class
(複製鏈接到瀏覽器查看喲)
Class代表類的實體,在運行的Java應用程序中表示類和接口 。
Java文件被編譯後,生成了.class文件,JVM此時就要去解讀.class文件 ,被編譯後的Java文件.class也被JVM解析爲一個對象,這個對象就是java.lang.Class .這樣當程序在運行時,每個java文件就最終變成了Class類對象的一個實例(老鐵們要注意,這個Class對象存放在方法區,不在堆裏面的)。我們通過Java的反射機制應用到這個實例,就可以去獲得甚至去添加改變這個類的屬性和動作,使得這個類成爲一個動態的類 。
(重要)常用獲得類相關的方法:
方法 | 用途 |
---|---|
getClassLoader() | 獲得類的加載器 |
getDeclaredClasses() | 返回一個數組,數組中包含該類中所有類和接口類的對象(包括私有的) |
forName(String className) | 根據類名返回類的對象 |
newInstance() | 創建類的實例 |
getName() | 獲得類的完整路徑名字 |
-
如何拿到Class對象?
共有3種方式可以獲得Class對象:
第一種:使用 Class.forName("類的全路徑名"); 靜態方法。
前提:已明確類的全路徑名。
第二種:使用類名.class方法。
說明:僅適合在編譯前就已經明確要操作的 Class。
第三種:使用類對象的 getClass()方法
下面我們來一次介紹,直接上代碼:
/**
* Created with IntelliJ IDEA.
* Description:
* User: GAOBO
* Date: 2020-02-20
* Time: 15:24
*/
class Student{
//私有屬性name
private String name = "bit";
//公有屬性age
public int age = 18;
//不帶參數的構造方法
public Student(){
System.out.println("Student()");
}
private Student(String name,int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,name)");
}
private void eat(){
System.out.println("i am eat");
}
public void sleep(){
System.out.println("i am pig");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
/*
1.通過getClass獲取Class對象
*/
Student s1 = new Student();
Class c1 = s1.getClass();
/*
2.直接通過 類名.class 的方式得到,該方法最爲安全可靠,程序性能更高
這說明任何一個類都有一個隱含的靜態成員變量 class
*/
Class c2 = Student.class;
/*
3、通過 Class 對象的 forName() 靜態方法來獲取,用的最多,
但可能拋出 ClassNotFoundException 異常
*/
Class c3 = null;
try {
//注意這裏是類的全路徑,如果有包需要加包的路徑
c3 = Class.forName("Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//一個類在 JVM 中只會有一個 Class 實例,即我們對上面獲取的
//c1,c2,c3進行 equals 比較,發現都是true
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c2.equals(c3));
}
}
輸出結果:true true true
到這裏,我們已經完成了反射的第一步,拿到了Class對象。我們可以看到打印結果,輸出都是true.這也印證了,Class對象本身,對於每個類來說,只有一份。且,他是在方法區的。
三、反射過程中其他類及方法的介紹
我們上面講過,反射就是在運行過程當中,動態的獲取類的屬性,方法等。那麼這些屬性和方法獲取到了之後,如何接收這些類型呢?我們來介紹一下,剩餘的類型吧!
-
(重要)常用獲得類相關的方法
方法 | 用途 |
---|---|
getClassLoader() | 獲得類的加載器 |
getDeclaredClasses() | 返回一個數組,數組中包含該類中所有類和接口類的對象(包括私有的) |
forName(String className) | 根據類名返回類的對象 |
newInstance() | 創建類的實例 |
getName() | 獲得類的完整路徑名字 |
-
(重要)常用獲得類中屬性相關的方法(以下方法返回值爲Field相關)
Field相關拓展鏈接:
https://developer.android.google.cn/reference/java/lang/reflect/Field
(複製鏈接到瀏覽器查看喲)
方法 | 用途 |
---|---|
getField(String name) | 獲得某個公有的屬性對象 |
getFields() | 獲得所有公有的屬性對象 |
getDeclaredField(String name) | 獲得某個屬性對象 |
getDeclaredFields() | 獲得所有屬性對象 |
-
(瞭解)獲得類中註解相關的方法
方法 | 用途 |
---|---|
getAnnotation(Class<A> annotationClass) | 返回該類中與參數類型匹配的公有註解對象 |
getAnnotations() | 返回該類所有的公有註解對象 |
getDeclaredAnnotation(Class<A> annotationClass) | 返回該類中與參數類型匹配的所有註解對象 |
getDeclaredAnnotations() | 返回該類所有的註解對象 |
-
(重要)獲得類中構造器相關的方法(以下方法返回值爲Constructor相關)
Constructor相關拓展鏈接:
https://developer.android.google.cn/reference/java/lang/reflect/Constructor
(複製鏈接到瀏覽器查看喲)
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 獲得該類中與參數類型匹配的公有構造方法 |
getConstructors() | 獲得該類的所有公有構造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 獲得該類中與參數類型匹配的構造方法 |
getDeclaredConstructors() | 獲得該類所有構造方法 |
-
(重要)獲得類中方法相關的方法(以下方法返回值爲Method相關)
Method相關拓展鏈接:
https://developer.android.google.cn/reference/java/lang/reflect/Method
(複製鏈接到瀏覽器查看喲)
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 獲得該類某個公有的方法 |
getMethods() | 獲得該類所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 獲得該類某個方法 |
getDeclaredMethods() | 獲得該類所有方法 |
以上我們大概列舉出了很多重要的類當中的方法,具體如何使用,我們接下來看看。
接下來我們開始使用反射,我們依舊反射上面的Student類,把反射的邏輯寫到另外的類中,以便進行理解。
注意:所有和反射相關的包都在import java.lang.reflect 包下面。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created with IntelliJ IDEA.
* Description:
* User: GAOBO
* Date: 2020-02-20
* Time: 16:31
*/
public class ReflectClassDemo {
// 創建對象
public static void reflectNewInstance() {
try {
Class<?> classStudent = Class.forName("Student");
Object objectStudent = classStudent.newInstance();
Student student = (Student) objectStudent;
System.out.println("獲得學生對象:"+student);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有的構造方法 屏蔽內容爲獲得公有的構造方法
public static void reflectPrivateConstructor() {
try {
Class<?> classStudent = Class.forName("Student");
//注意傳入對應的參數
Constructor<?> declaredConstructorStudent = classStudent.getDeclaredConstructor(String.class,int.class);
//Constructor<?> declaredConstructorStudent = classStudent.getConstructor();
//設置爲true後可修改訪問權限
declaredConstructorStudent.setAccessible(true);
Object objectStudent = declaredConstructorStudent.newInstance("高博",15);
//Object objectStudent = declaredConstructorStudent.newInstance();
Student student = (Student) objectStudent;
System.out.println("獲得私有構造哈數且修改姓名和年齡:"+student);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有屬性
public static void reflectPrivateField() {
try {
Class<?> classStudent = Class.forName("Student");
Field field = classStudent.getDeclaredField("name");
field.setAccessible(true);
//可以修改該屬性的值
Object objectStudent = classStudent.newInstance();
Student student = (Student) objectStudent;
field.set(student,"小明");
String name = (String) field.get(student);
System.out.println("反射私有屬性修改了name:"+ name);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有方法
public static void reflectPrivateMethod() {
try {
Class<?> classStudent = Class.forName("Student");
Method methodStudent = classStudent.getDeclaredMethod("function",String.class);
System.out.println("私有方法的方法名爲:"+methodStudent.getName());
//私有的一般都要加
methodStudent.setAccessible(true);
Object objectStudent = classStudent.newInstance();
Student student = (Student) objectStudent;
methodStudent.invoke(student,"我是給私有的function函數傳的參數");
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
//reflectNewInstance();
//reflectPrivateConstructor();
//reflectPrivateField();
reflectPrivateMethod();
}
}
四、反射優點和缺點
優點:
1.對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法;
2.增加程序的靈活性和擴展性,降低耦合性,提高自適應能力;
3.反射已經運用在了很多流行框架如:Struts、Hibernate、Spring 等等。
缺點:
1.反射會有效率問題,會降低程序的效率;
2.反射技術繞過了源代碼的技術,會帶來維護問題。反射代碼比相應的直接代碼更復雜 。
五、總結
好了,以上就是反射相關的一些重要知識,以及一些簡單的應用,在後期學習框架相關的時候,大家就會更好的理解反射了!