Java反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為Java語言的反射機制。反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。這一概念的提出很快引發了計算機科學領域關于應用反射性的研究。它首先被程序語言的設計領域所采用,并在Lisp和面向對象方面取得了成績。當然反射本身并不是一個新概念,它可能會使我們聯想到光學中的反射概念,盡管計算機科學賦予了反射概念新的含義,但是,從現象上來說,它們確實有某些相通之處,這些有助于我們的理解。
Java反射機制主要提供下面幾種用途:
在運行時判斷任意一個對象所屬的類
在運行時構造任意一個類的對象
在運行時判斷任意一個類所具有的成員變量和方法
在運行時調用任意一個對象的方法
首先看一個簡單的例子,通過這個例子來理解Java的反射機制是如何工作的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package com.wanggc.reflection; import java.lang.reflect.Method; /** * Java 反射練習。 * * @author Wanggc */ public class ForNameTest { /** * 入口函數。 * * @param args * 參數 * @throws Exception * 錯誤信息 */ public static void main(String[] args) throws Exception { // 獲得Class Class<?> cls = Class.forName(args[ 0 ]); // 通過Class獲得所對應對象的方法 Method[] methods = cls.getMethods(); // 輸出每個方法名 for (Method method : methods) { System.out.println(method); } } } |
當傳入的參數是java.lang.String時,會輸出如下結果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
public boolean java.lang.String.equals(java.lang.Object) public java.lang.String java.lang.String.toString() public int java.lang.String.hashCode() public int java.lang.String.compareTo(java.lang.String) public int java.lang.String.compareTo(java.lang.Object) public int java.lang.String.indexOf( int ) public int java.lang.String.indexOf( int , int ) public int java.lang.String.indexOf(java.lang.String) public int java.lang.String.indexOf(java.lang.String, int ) public static java.lang.String java.lang.String.valueOf( int ) public static java.lang.String java.lang.String.valueOf( char ) public static java.lang.String java.lang.String.valueOf( boolean ) public static java.lang.String java.lang.String.valueOf( float ) public static java.lang.String java.lang.String.valueOf( char [], int , int ) public static java.lang.String java.lang.String.valueOf( double ) public static java.lang.String java.lang.String.valueOf( char []) public static java.lang.String java.lang.String.valueOf(java.lang.Object) public static java.lang.String java.lang.String.valueOf( long ) public char java.lang.String.charAt( int ) public int java.lang.String.codePointAt( int ) public int java.lang.String.codePointBefore( int ) public int java.lang.String.codePointCount( int , int ) public int java.lang.String.compareToIgnoreCase(java.lang.String) public java.lang.String java.lang.String.concat(java.lang.String) public boolean java.lang.String.contains(java.lang.CharSequence) public boolean java.lang.String.contentEquals(java.lang.CharSequence) public boolean java.lang.String.contentEquals(java.lang.StringBuffer) public static java.lang.String java.lang.String.copyValueOf( char []) public static java.lang.String java.lang.String.copyValueOf( char [], int , int ) public boolean java.lang.String.endsWith(java.lang.String) public boolean java.lang.String.equalsIgnoreCase(java.lang.String) public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]) public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]) public byte [] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException public void java.lang.String.getBytes( int , int , byte [], int ) public byte [] java.lang.String.getBytes() public byte [] java.lang.String.getBytes(java.nio.charset.Charset) public void java.lang.String.getChars( int , int , char [], int ) public native java.lang.String java.lang.String.intern() public boolean java.lang.String.isEmpty() public int java.lang.String.lastIndexOf(java.lang.String) public int java.lang.String.lastIndexOf( int , int ) public int java.lang.String.lastIndexOf( int ) public int java.lang.String.lastIndexOf(java.lang.String, int ) public int java.lang.String.length() public boolean java.lang.String.matches(java.lang.String) public int java.lang.String.offsetByCodePoints( int , int ) public boolean java.lang.String.regionMatches( boolean , int ,java.lang.String, int , int ) public boolean java.lang.String.regionMatches( int ,java.lang.String, int , int ) public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence) public java.lang.String java.lang.String.replace( char , char ) public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String) public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String) public java.lang.String[] java.lang.String.split(java.lang.String) public java.lang.String[] java.lang.String.split(java.lang.String, int ) public boolean java.lang.String.startsWith(java.lang.String) public boolean java.lang.String.startsWith(java.lang.String, int ) public java.lang.CharSequence java.lang.String.subSequence( int , int ) public java.lang.String java.lang.String.substring( int ) public java.lang.String java.lang.String.substring( int , int ) public char [] java.lang.String.toCharArray() public java.lang.String java.lang.String.toLowerCase() public java.lang.String java.lang.String.toLowerCase(java.util.Locale) public java.lang.String java.lang.String.toUpperCase() public java.lang.String java.lang.String.toUpperCase(java.util.Locale) public java.lang.String java.lang.String.trim() public final native void java.lang.Object.wait( long ) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait( long , int ) throws java.lang.InterruptedException public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() |
這樣就列出了java.lang.String類的所有方法名、及其限制符、返回類型及拋出的異常。這個程序使用Class類forName方法載入指定的類,然后調用getMethods方法返回指定類的方法列表。java.lang.reflect.Method用來表述某個類中的單一方法。
使用java的反射機制,一般需要遵循三步:
獲得你想操作類的Class對象
通過第一步獲得的Class對象去取得操作類的方法或是屬性名
操作第二步取得的方法或是屬性
Java運行的時候,某個類無論生成多少個對象,他們都會對應同一個Class對象,它表示正在運行程序中的類和接口。如何取得操作類的Class對象,常用的有三種方式:
調用Class的靜態方法forName,如上例;
使用類的.class語法,如:Class<?> cls = String.class;
調用對象的getClass方法,如:String str = "abc";Class<?> cls = str .getClass();
下面將通過實例講述如何通過前面所訴的三步來執行某對象的某個方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.wanggc.reflection; import java.lang.reflect.Method; /** * Java 反射練習。 * * @author Wanggc */ public class ReflectionTest { public static void main(String[] args) throws Exception { DisPlay disPlay = new DisPlay(); // 獲得Class Class<?> cls = disPlay.getClass(); // 通過Class獲得DisPlay類的show方法 Method method = cls.getMethod( "show" , String. class ); // 調用show方法 method.invoke(disPlay, "Wanggc" ); } } class DisPlay { public void show(String name) { System.out.println( "Hello :" + name); } } |
前面說過,Java程序的每個類都會有個Class對象與之對應。Java反射的第一步就是獲得這個Class對象,如代碼14行。當然,每個類的方法也必有一個Method對象與之對應。要通過反射的方式調用這個方法,就要首先獲得這個方法的Method對象,如代碼16行,然后用Method對象反過來調用這個方法,如代碼18行。注意16行getMethod方法的第一個參數是方法名,第二個是此方法的參數類型,如果是多個參數,接著添加參數就可以了,因為getMethod是可變參數方法。執行18行代碼的invoke方法,其實也就是執行show方法,注意invoke的第一個參數,是DisPlay類的一個對象,也就是調用DisPlay類哪個對象的show方法,第二個參數是給show方法傳遞的參數。類型和個數一定要與16行的getMethod方法一直。
上例講述了如何通過反射調用某個類的方法,下面將再通過一個實例講述如何通過反射給某個類的屬性賦值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
package com.wanggc.reflection; import java.lang.reflect.Field; /** * Java 反射之屬性練習。 * * @author Wanggc */ public class ReflectionTest { public static void main(String[] args) throws Exception { // 建立學生對象 Student student = new Student(); // 為學生對象賦值 student.setStuName( "Wanggc" ); student.setStuAge(); // 建立拷貝目標對象 Student destStudent = new Student(); // 拷貝學生對象 copyBean(student, destStudent); // 輸出拷貝結果 System.out.println(destStudent.getStuName() + ":" + destStudent.getStuAge()); } /** * 拷貝學生對象信息。 * * @param from * 拷貝源對象 * @param dest * 拷貝目標對象 * @throws Exception * 例外 */ private static void copyBean(Object from, Object dest) throws Exception { // 取得拷貝源對象的Class對象 Class<?> fromClass = from.getClass(); // 取得拷貝源對象的屬性列表 Field[] fromFields = fromClass.getDeclaredFields(); // 取得拷貝目標對象的Class對象 Class<?> destClass = dest.getClass(); Field destField = null ; for (Field fromField : fromFields) { // 取得拷貝源對象的屬性名字 String name = fromField.getName(); // 取得拷貝目標對象的相同名稱的屬性 destField = destClass.getDeclaredField(name); // 設置屬性的可訪問性 fromField.setAccessible( true ); destField.setAccessible( true ); // 將拷貝源對象的屬性的值賦給拷貝目標對象相應的屬性 destField.set(dest, fromField.get(from)); } } } /** * 學生類。 */ class Student { /** 姓名 */ private String stuName; /** 年齡 */ private int stuAge; /** * 獲取學生姓名。 * * @return 學生姓名 */ public String getStuName() { return stuName; } /** * 設置學生姓名 * * @param stuName * 學生姓名 */ public void setStuName(String stuName) { this .stuName = stuName; } /** * 獲取學生年齡 * * @return 學生年齡 */ public int getStuAge() { return stuAge; } /** * 設置學生年齡 * * @param stuAge * 學生年齡 */ public void setStuAge( int stuAge) { this .stuAge = stuAge; } } |
Java的發射機制中類有Class對應,類的方法有Method對應,當然屬性也有Field與之對應。代碼中注釋已經做了詳細的注釋,在此不再贅述。但要注意,Field提供了get和set方法獲取和設置屬性的值,但是由于屬性是私有類型,所以需要設置屬性的可訪問性為true,如代碼50~51行。也可以在為整個fields設置可訪問性,在40行下面使用AccessibleObject的靜態方法setAccessible,如:AccessibleObject.setAccessible(fromFields, true);
前面講述了如何用Java反射機制操作一個類的方法和屬性,下面再通過一個實例講述如何在運行時創建類的一個對象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
package com.wanggc.reflection; import java.lang.reflect.Field; /** * Java 反射之屬性練習。 * * @author Wanggc */ public class ReflectionTest { public static void main(String[] args) throws Exception { // 建立學生對象 Student student = new Student(); // 為學生對象賦值 student.setStuName( "Wanggc" ); student.setStuAge(); // 建立拷貝目標對象 Student destStudent = (Student) copyBean(student); // 輸出拷貝結果 System.out.println(destStudent.getStuName() + ":" + destStudent.getStuAge()); } /** * 拷貝學生對象信息。 * * @param from * 拷貝源對象 * @param dest * 拷貝目標對象 * @throws Exception * 例外 */ private static Object copyBean(Object from) throws Exception { // 取得拷貝源對象的Class對象 Class<?> fromClass = from.getClass(); // 取得拷貝源對象的屬性列表 Field[] fromFields = fromClass.getDeclaredFields(); // 取得拷貝目標對象的Class對象 Object ints = fromClass.newInstance(); for (Field fromField : fromFields) { // 設置屬性的可訪問性 fromField.setAccessible( true ); // 將拷貝源對象的屬性的值賦給拷貝目標對象相應的屬性 fromField.set(ints, fromField.get(from)); } return ints; } } /** * 學生類。 */ class Student { /** 姓名 */ private String stuName; /** 年齡 */ private int stuAge; /** * 獲取學生姓名。 * * @return 學生姓名 */ public String getStuName() { return stuName; } /** * 設置學生姓名 * * @param stuName * 學生姓名 */ public void setStuName(String stuName) { this .stuName = stuName; } /** * 獲取學生年齡 * * @return 學生年齡 */ public int getStuAge() { return stuAge; } /** * 設置學生年齡 * * @param stuAge * 學生年齡 */ public void setStuAge( int stuAge) { this .stuAge = stuAge; } } |
此例和上例運行的結果是相同的。但是copyBean方法返回的對象不再是外面傳入的,而是由方法內部產生的,如第40行代碼所示。注意:Class的newInstance方法,只能創建只包含無參數的構造函數的類,如果某類只有帶參數的構造函數,那么就要使用另外一種方式:fromClass.getDeclaredConstructor(int.class,String.class).newInstance(24,"wanggc");
至此,Java反射機制的常用機能(運行時調用對象的方法、類屬性的使用、創類類的對象)已經介紹完了。
補充:在獲得類的方法、屬性、構造函數時,會有getXXX和getgetDeclaredXXX兩種對應的方法。之間的區別在于前者返回的是訪問權限為public的方法和屬性,包括父類中的;但后者返回的是所有訪問權限的方法和屬性,不包括父類的。
以上內容就是給大家介紹的java發射機制,希望大家喜歡。