一、什麼叫Java反射機制?
Java中的反射機制是指在運行狀態中,對於任意一個類,能夠動態獲取這個類中的屬性和方法;對於任意一個對象,都能夠任意調用它的屬性和方法。這種動態獲取類的信息以及動態調用對象方法的功能稱爲Java的反射機制。總結就是:反射可以實現運行時知道任意一個類的屬性和方法。
二、Java當中爲什麼需要反射機制?工作原理是什麼?
要明白爲什麼需要反射機制,首先就得先清楚兩個概念:
(1)靜態編譯:在編譯時就必須要知道變量得類型才能編譯通過,比如C++,Java等編譯之前必須清楚的指明數據類型;
(2)動態編譯:顯然和上述概念相反,即在編譯時不需要立即知道變量的類型,在運行時指明其類型就行。
有上述概念之後便有了靜態語言和動態語言之分,而Java,C++就是典型的靜態語言,而Python,Ruby等則爲動態語言。但是Java雖是一個靜態的解釋型語言,但其也有動態的性質------反射(Reflection)。用在Java身上指的是運行時加載,探知,使用編譯期間未知的classes。
下面探討一下反射是如何在僅知道類名的情況下能夠知道整個類的完整構造的(方法,屬性等)。
首先我們都清楚,Java是一種解釋型的語言,即編譯器首先將源碼編譯成class文件,然後虛擬機(JVM)再將class文件解釋給目標計算機執行。而這裏所說的反射機制操作的其實就是源碼編譯之後的class文件。首先將class文件加載到內存,然後從該文件中解剖出某個具體類的構造函數,方法和屬性,從而完全知道某個類的所有構造。下面給出一個簡易的demo及字節碼文件。demo源碼:
public class Student {
String name="xiaoshuang";
String gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
private void setGender(String gender) {
this.gender = gender;
}
public Student(String name, String gender) {
this.name = name;
this.gender = gender;
}
public Student(){}
public static void main(String[] args) {
//獲取該類的方
try {
Class c = Class.forName("com.tyf.reflect.Student");
Method methods[] = c.getDeclaredMethods();//獲取該類的所有方法
Method methods2[]=c.getMethods();//獲取該類以及父類的所有public類
for(Method m:methods){
System.out.println(m.getName());
}
}catch (Exception e){
e.printStackTrace();
}
}
字節碼文件:
public class com.tyf.reflect.Student {
java.lang.String name;
java.lang.String gender;
public java.lang.String getName();
Code:
0: aload_0
1: getfield #1 // Field name:Ljava/lang/String;
4: areturn
public void setName(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field name:Ljava/lang/String;
5: return
public java.lang.String getGender();
Code:
0: aload_0
1: getfield #2 // Field gender:Ljava/lang/String;
4: areturn
public com.tyf.reflect.Student(java.lang.String, java.lang.String);
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #4 // String xiaoshuang
7: putfield #1 // Field name:Ljava/lang/String;
10: aload_0
11: aload_1
12: putfield #1 // Field name:Ljava/lang/String;
15: aload_0
16: aload_2
17: putfield #2 // Field gender:Ljava/lang/String;
20: return
public com.tyf.reflect.Student();
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #4 // String xiaoshuang
7: putfield #1 // Field name:Ljava/lang/String;
10: return
public static void main(java.lang.String[]);
Code:
0: new #5 // class java/util/HashSet
3: dup
4: invokespecial #6 // Method java/util/HashSet."<init>":()V
7: astore_1
8: aload_1
9: ldc #7 // String tom
11: invokeinterface #8, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: invokevirtual #9 // Method java/lang/Object.getClass:()Ljava/lang/Class;
21: astore_2
22: aload_2
23: ldc #10 // String add
25: iconst_1
26: anewarray #11 // class java/lang/Class
29: dup
30: iconst_0
31: ldc #12 // class java/lang/Object
33: aastore
34: invokevirtual #13 // Method java/lang/Class.getDeclaredMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
37: astore_3
38: aload_3
39: aload_1
40: iconst_1
41: anewarray #12 // class java/lang/Object
44: dup
45: iconst_0
46: bipush 100
48: invokestatic #14 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
51: aastore
52: invokevirtual #15 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
55: pop
56: aload_1
57: invokeinterface #16, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
62: astore 4
64: aload 4
66: invokeinterface #17, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
71: ifeq 94
74: aload 4
76: invokeinterface #18, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
81: astore 5
83: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
86: aload 5
88: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
91: goto 64
94: goto 102
97: astore_2
98: aload_2
99: invokevirtual #22 // Method java/lang/Exception.printStackTrace:()V
102: return
Exception table:
from to target type
17 94 97 Class java/lang/Exception
}
從上面的字節碼文件中我們很清楚的能看到類裏面的一下public方法和屬性。非常值得注意的一點是注意類名,在字節碼中使用的是該類的全類名(包名+類名)。因此呢,在使用反射時,forName()方法需要指定該類的全類名,從而從該class文件中可以直接獲取原類的完整結構(屬性,方法等)。
三、反射的使用及入門
在使用反射之前,最好清楚一個這樣的概念,即類類型。在編程語言中,各種數據都有其數據類型,那麼對於類本身而言,它也有自己的類型,即類類型,其實和普通的數據類型差不多,清楚這一點就行。類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)。對於普通的類實例化時只需要new一個就行,但是要想用new實例化Class是行不通的,因爲Class的構造器是私有的(自行查看源碼)。
1、獲取某個類(Student)的Class對象的三種方法:
(1)Class c1=Student.class;//通過Student類隱藏的靜態變量來獲取
(2)Class c2=Student.getClass();//通過Stduent的getClass()方法
(3)Class c3=Student.forName("com.tyf.reflect.Student");//com.tyf.reflect.Student爲Student類的全類名。
注意:這裏的c1,c2,c3是完全一樣的,都是Class的對象(學名 類類型)。這裏不必糾結Student和c1,c2,c3是否一樣了(都叫Class對象),只需瞭解類類型是什麼就行。
2、反射中的相關操作
(1)如何獲取類的方法以及方法的執行:
try{
Class c = Student.class;
Object o = c.newInstance();//初始化一個實例
Method method_setName = c.getDeclaredMethod("setName", String.class);
Method method_getName = c.getDeclaredMethod("getName");
method_setName.invoke(o,"yafneg");
Object name = method_getName.invoke(o);
System.out.println(name);
}catch (Exception e){
e.printStackTrace();
}
如何來獲取該類的所有方法呢?可以用如下兩種方法:
Method methods[] = c.getDeclaredMethods();//獲取該類的所有方法
Method methods2[]=c.getMethods();//獲取該類以及父類的所有public類
兩者的區別上述註釋已經說的很清楚,完整代碼如下:
try {
Class c = Class.forName("com.tyf.reflect.Student");
Method methods[] = c.getDeclaredMethods();//獲取該類的所有方法
// Method methods2[]=c.getMethods();//獲取該類以及父類的所有public類
for(Method m:methods){
System.out.println(m.getName());
}
}catch (Exception e){
e.printStackTrace();
}
執行getDeclaredMethods(),並輸出方法名的結果如下:
main
getName
setName
setGender
getGender
從上述結果可以看到,getDeclaredMethods()獲取的是該類的所有方法(public+private)。下面的獲取屬性的方法結果於上述類似,不再贅述。
執行getMethods(),並輸出方法名的結果如下:
main
getName
setName
getGender
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
從上面的結果中可以看到,該方法不但獲得了Student類的public方法,同時也獲得了Student父類(Object)的public方法,注意未獲得setGender()方法,因爲其是private方法。
(2)獲取類屬性:
Field field = c.getDeclaredField("name"); //獲取指定屬性
//Field fields[]=c.getDeclaredFields();//獲取所有屬性
//Field fields[]=c.getFields();獲取所有的public子類及父類屬性
field.setAccessible(true);//設置是否允許訪問,因爲該屬性是privated的
完整代碼:
try {
Class c = Class.forName("com.tyf.reflect.Student");
Object o = c.newInstance();
Field field = c.getDeclaredField("name");
Field fields[] = c.getDeclaredFields();//獲取所有屬性
//Field fields[]=c.getFields();獲取所有的public子類及父類屬性
field.setAccessible(true);//設置是否允許訪問,因爲該屬性是privated的
Object name = field.get(o);//獲取該屬性值
System.out.println(name);
} catch (Exception e) {
e.printStackTrace();
}
執行結果:
xiaoshuang
至於getDeclaredField()和getField()的區別和上述getDeclardMethod()與getMethods()是一樣的,這裏不再贅述。
(3)獲取類的構造器:
try{
Class c = Class.forName("com.tyf.reflect.Student");
Constructor constructor = c.getDeclaredConstructor(String.class,String.class);//獲取含參的構造器
Object o = constructor.newInstance("yafeng","boy");//利用反射得到的構造器實例化對象
Field field = c.getDeclaredField("name");//獲取name屬性
Object name = field.get(o);//獲取屬性值
System.out.println(name);
}catch (Exception e){
e.printStackTrace();
}
}
執行結果:
yafeng
四、Java反射機制的應用
當在某集合中需要放多種數據類型的數據時,可以使用反射機制來解決。
爲什麼可以使用反射機制呢?需要注意的是集合中的泛型只在程序編譯階段有效,而在運行期間無效(相當於沒有),而反射機制正好就是運行期間才檢測數據檢測。看下面代碼:
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("tom");
//我要給set中要放一個int型的數據,用set.add(11);是會報錯的,那麼改怎麼做?
Class c = set.getClass();
try {
Method m = c.getDeclaredMethod("add",Object.class);
m.invoke(set,100);
}catch (Exception e){
e.printStackTrace();
}
for(Object s:set){
System.out.println(s);
}
}
執行結果:
tom
100
上述的內容即爲反射當中常用到的一些東西,掌握這些知識,Java反射基本已經入門。當然Java反射不止這些東西,需要學的還有很多,還需要day day up。