反射是什么鬼
反射其實就是允許我們獲取目標類的方法、成員變量等信息,以及可以調用、改變某些方法和成員變量的值。( JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的以及動態調用對象的方法的功能稱為Java的反射機制。)--莎士比亞
詳細見某百科反射百科,這個不是重點,下面開始反射之旅。
反射的使用
先上一張反射相關知識點的腦圖,如果對腦圖上東西一竅不通或者有些模糊,那么請往下看。
反射機制相關的類
java.lang.Class; //類
java.lang.reflect.Constructor;//構造方法
java.lang.reflect.Field; //類的成員變量
java.lang.reflect.Method;//類的方法
java.lang.reflect.Modifier;//訪問權限
java.lang.reflect.Parameter;//方法參數
Class和class的區別
class:小寫字母c開頭的class是聲明一個類的關鍵字。
Class:在Java中,反射的源頭就是Class,每個class都有一個相應的Class對象。也就是說,當我們編寫一個類,編譯完成后,在生成的.class文件中,就會產生一個Class對象,它封裝了這個類的信息,包括上面說的反射機制相關的類等信息。
Class的獲取
上面說了,Class類用于封裝類的各種信息,它是反射的源頭,獲取一個類的Class通常有以下三種方法:Class cls = A.class;
A a = new A();
Class cls = a.getClass();Class cls = Class.forName("com.xx.A");
-
通過獲取到的Class生成一個實例對象
A a = (A) cls.newInstance(); //獲取A的實例對象
假設我們有一個Car類,里面有一個start()方法,我們可以以下方法得到Car類的實例并調用start()方法,注意,需要無參構造函數:
反射獲取Car實例.png 類信息的獲取
獲取類的方法API | 說明 |
---|---|
Method[] methods = cls.getDeclaredMethods() | 獲取當前類聲明的所有方法列表(無視權限修飾符) |
Method[] methods = cls.getMethods() | 獲取當前類及父類所有public方法列表 |
Method method = cls.getDeclaredMethod(name, parameterTypes) | 獲取當前類聲明的某個特定方法 |
Method method = cls.getMethod(name, parameterTypes) | 獲取當前類或父類某個特定public方法 |
獲取類的成員變量API | 說明 |
---|---|
Field[] fields = cls.getDeclaredFields() | 獲取當前類聲明的所有成員變量列表(無視權限修飾符) |
Field[] fields = cls.getFields() | 獲取當前類及父類所有public成員變量列表 |
Field field = cls.getDeclaredField(name) | 獲取當前類聲明的某個特定成員變量 |
Field field = cls.getField(name) | 獲取當前類或父類某個特定public成員變量 |
獲取類的構造函數API | 說明 |
---|---|
Constructor[] constructors = cls.getDeclaredConstructors() | 獲取當前類聲明的所有成員變量列表(無視權限修飾符) |
Constructor[] constructors = cls.getConstructors() | 獲取當前類及父類所有public構造函數列表 |
Constructor constructor = cls.getDeclaredConstructor(parameterTypes) | 獲取當前類聲明的某個特定構造函數 |
Constructor constructor = cls.getConstructor(parameterTypes) | 獲取當前類或父類某個特定public構造函數 |
獲取類或方法的修飾符API | 說明 |
---|---|
int modifiers = cls.getModifiers() | 獲取類的修飾符,注意返回值是int類型,可通過Modifier.toString(modifiers)獲取到相對應的String類型 |
int modifiers = method.getModifiers() | 獲取方法的修飾符,同上 |
修飾符返回值int類型對應的修飾符如下圖:
獲取方法的參數API | 說明 |
---|---|
Class[] parameterTypes =method.getParameterTypes() | 獲取方法參數的類類型列表(即.class,如int.class) |
Parameter[] parameters = method.getParameters() | 獲取方法參數列表(即.class,如int.class) |
獲取編程元素上的注解API | 說明 |
---|---|
Annotation[] annotations = cls(/method/field).getAnnotations() | 獲取類/方法/成員變量上標注的所有注解 |
Annotation[] declaredAnnotations = cls(/method/field).getDeclaredAnnotations() | 獲取類/方法/成員變量上標注的所有注解 |
Override annotation = cls(/method/field).getAnnotation(Override.class) | 獲取類/方法/成員變量上某個特定注解 |
Annotation[][] parameterAnnotations = m.getParameterAnnotations() | 獲取方法參數上的注解 |
boolean b = cls(/method/field).isAnnotationPresent(Bind.class) | 該變成元素上是否有Bind.class注解 |
getDeclaredAnnotation(s):返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。
getAnnotation(s):返回此元素上存在的所有注釋。(如果此元素沒有注釋,則返回長度為零的數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。
getDeclaredAnnotations得到的是當前成員所有的注釋,不包括繼承的。而getAnnotations得到的是包括繼承的所有注釋。
關鍵在于繼承的問題上,getDeclaredAnnotations和getAnnotations是否相同,就在于父類的注解是否可繼承,這可以用sun.reflect.annotation.AnnotationType antype3=AnnotationType.getInstance(Class.forName(annotationtype_class(example:"javax.ejb.Stateful")).isInherited())來判定,如果為true,說明可以被繼承則存在與getAnnotations之中而不在getDeclaredAnnotations之中,否則,也不存在與getannnotations中,因為不能被繼承。
- 通過反射獲取類中某個方法并調起
//我們有這個Car類
public class Car {
@Override
public void start() {
System.out.println("Car start!");
}
}
Car car = new Car();
Class cls = car.getClass();//獲取類類型
Method method = cls.getDeclaredMethod("start", new Class[]{});//獲取start方法,第一個參數為方法名字,
//第二個參數是一個可變參數,接收的是方法的參數的類類型列表,此處Car里面有一個方法是無參的start(),則傳入new Class[]{};同理,假設Car類還有一個重載的start(int speed)方法,則我們應該通過cls.getDeclaredMethod("start", new Class[]{int.class});
//method.setAccessible(true);//注意如果目標方法是private修飾的,則需要先調用setAccessible(true)破封裝,此處Car類里的start方法是public,所以不需要調用
Object obj = method.invoke(car, new Class[]{});//傳入的和上面的獲取方法參數同理,此處不說。
//需要說明的是如果方法沒有返回值,則返回是null,即我們這里接收到的obj是null;如果對應方法有返回值則返回具體的返回值。
- 通過反射獲取類中某個成員變量并修改它的值
//1.獲取intField成員變量
Field declaredField = cls.getDeclaredField("intField");
//2.如果該成員變量是private,此時我們應該先設置為可操作
declaredField.setAccessible(true);
//3.賦值
declaredField.set(ca, 5);```
## 舉個栗子
下面通過一個栗子把上面所說反射的使用全部串聯起來。需求:打印類中所有信息,并修改某個成員變量。具體看注釋,并且跟著手打一遍印象深刻點,這里直接上代碼。
首先我們聲明一個父類:BaseActivity
```java
public class BaseActivity {
public int basePublicFiled;//父類公有成員變量
private String basePrivateFiled;//父類私有成員變量
private BaseActivity(){
//父類私有構造函數
}
public BaseActivity(int i){
//父類公有構造函數
}
public void basePublicMethod(String str){
//父類公有方法
}
private void basePrivateMethod(String str){
//父類私有方法
}
}
然后聲明一個ChildActivity繼承它:
public class ChildActivity extends BaseActivity{
public ChildActivity(int i) {
super(i);
}
public int childPublicFiled;
private String childPrivateFiled;
public void childPublicMethod(String str){
//no-op
}
private void childPrivateMethod(String str){
//no-op
}
private int intField = 0;//注意,這里值是0,待會反射修改這個成員變量的值
public void plus(int i){
//待會反射調用此方法,注意看原始值和反射后打印出來的結果
System.out.println(String.valueOf(i) +" + "+ String.valueOf(intField) + " = " + (i + intField));
}
}
編寫核心類,取名ClassMessageGetter,用于獲取反射操作
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import entity.ChildActivity;
public class ClassMessageGetter {
/**
* 獲取某個類類信息
* @param obj
*/
public static void getMethodMessage(Object obj){
//獲取方法
//1.getMethods()獲取該類所有public,包括父類的public方法
//2.getDeclaredMethods()獲取當前類(不含父類)的所有方法(與方法訪問權限無關)
System.out.println("1.getMethods()獲取該類所有public,包括父類的public方法\n");
Class cls = obj.getClass();
Method[] methods = cls.getMethods();
Method[] declaredMethods = cls.getDeclaredMethods();
for(Method method : methods){
//通過method.getReturnType().getName()獲取返回值名稱
//通過method.getName()獲取方法名稱
int modifiers = cls.getModifiers();
System.out.print(Modifier.toString(method.getModifiers())+" "+method.getReturnType().getName() + " " + method.getName()+"(");
//通過method.getParameterTypes();獲取方法參數的類類型
Class[] parameterTypes = method.getParameterTypes();
//通過method.getParameters()獲取方法參數
Parameter[] parameters = method.getParameters();
for(int i = 0 ; i < parameterTypes.length ; i ++){
System.out.print(parameterTypes[i].getSimpleName() + " " +parameters[i].getName()+ (i!=parameterTypes.length-1?",":""));
}
System.out.println(")");
}
System.out.println("\n-----------------------------------------------------------\n");
System.out.println("2.getDeclaredMethods()獲取當前類(不含父類)的所有方法(與方法訪問權限無關)\n");
for(Method method : declaredMethods){
System.out.println(method.getName());
}
}
/**
* 獲取類成員變量信息
* @param obj
*/
public static void getFieldMessage(Object obj){
Class cls = obj.getClass();
//1.getFields()獲取該類所有public,包括父類的public成員變量
Field[] fields = cls.getFields();
//2.getDeclaredFields()獲取當前類(不含父類)的所有成員變量(與變量訪問權限無關)
Field[] declaredFields = cls.getDeclaredFields();
System.out.println("cls.getFields() 獲取該類所有public,包括父類的public成員變量");
for (Field field : fields) {
System.out.println(field.getType().getSimpleName() + " "+field.getName());
}
System.out.println("\ngetDeclaredFields()獲取當前類(不含父類)的所有成員變量(與變量訪問權限無關)");
for (Field field : declaredFields) {
System.out.println(field.getType().getSimpleName() + " "+field.getName());
}
}
/**
* 獲取構造函數信息
* @param obj
*/
public static void getConstructorMessage(Object obj){
Class cls = obj.getClass();
Constructor[] constructors = cls.getConstructors();
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName());
}
System.out.println("------------------");
for (Constructor constructor : declaredConstructors) {
System.out.println(constructor.getName());
}
}
/**
* 通過反射操作方法和成員變量
*/
public static void reflectionProcess(){
ChildActivity ca = new ChildActivity(0);
Class cls = ca.getClass();
try {
//修改filed值
//1.獲取intField成員變量
Field declaredField = cls.getDeclaredField("intField");
//2.因為該成員變量是private,此時我們應該先設置為可操作
declaredField.setAccessible(true);
//3.賦值
declaredField.set(ca, 5);
//反射調用方法
//1.獲取目標方法plus(int i)
Method method = cls.getDeclaredMethod("plus", int.class);
//因為該方法是public的,因此不需要調用 method.setAccessible(true);
//2.調用method.invoke(對象,參數列表)
method.invoke(ca, 6);
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后寫一個測試類Demain進行測試:
public class Domain {
public static void main(String[] args) {
System.out.println("===================方法信息===================");
ClassMessageGetter.getMethodMessage(new ChildActivity(0));
System.out.println("===================成員信息===================");
ClassMessageGetter.getFieldMessage(new ChildActivity(0));
System.out.println("===================構造函數信息===================");
ClassMessageGetter.getConstructorMessage(new ChildActivity(0));
System.out.println("===================修改成員變量及調用方法信息===================");
ClassMessageGetter.reflectionProcess();
}
}
打印結果如下:
===================方法信息===================
1.getMethods()獲取該類所有public,包括父類的public方法
public void plus(int arg0)
public void childPublicMethod(String arg0)
public void basePublicMethod(String arg0)
public final void wait()
public final void wait(long arg0,int arg1)
public final native void wait(long arg0)
public boolean equals(Object arg0)
public java.lang.String toString()
public native int hashCode()
public final native java.lang.Class getClass()
public final native void notify()
public final native void notifyAll()
-----------------------------------------------------------
2.getDeclaredMethods()獲取當前類(不含父類)的所有方法(與方法訪問權限無關)
plus
childPublicMethod
childPrivateMethod
===================成員信息===================
cls.getFields() 獲取該類所有public,包括父類的public成員變量
int childPublicFiled
int basePublicFiled
getDeclaredFields()獲取當前類(不含父類)的所有成員變量(與變量訪問權限無關)
int childPublicFiled
String childPrivateFiled
int intField
===================構造函數信息===================
entity.ChildActivity
------------------
entity.ChildActivity
===================修改成員變量及調用方法信息===================
6 + 5 = 11
到此,Java反射基本就完結。最后說一個與反射相關的東西,叫“類型擦除”,它和泛型有關。
泛型是1.5中引入的一個新的概念,由于不用進行強制轉換類型了,所以具有較高的安全性和易用性。因為泛型其實只是在編譯器中實現的而虛擬機并不認識泛型類項,所以要在虛擬機中將泛型類型進行擦除。也就是說,在編譯階段使用泛型,運行階段取消泛型,即擦除。
就是說泛型機制其實是在編譯期進行“檢查”,因此,只要我們能夠繞過編譯期,就可以為所欲為。先看下面代碼:
可以看到,當我們add進去的類型不是String時,編譯器就會報錯,那么有什么辦法可以add進去int類型的值嗎?上面說了,泛型機制是在編譯期進行的,所以我們可以通過反射,add進去其他類型的值。
先看下面代碼:
從上面的代碼我們可以知道,兩個list的Class其實是同一份,從側面印證了泛型機制是在編譯期進行的。接下來即將發生神奇的事情:
最終結果size打印的是3,說明我們成功地往貓碗(List<String>)里面撒了狗糧(int)。
The End
轉載請注明出處。