什么是反射
“反射(Reflection)能夠讓運行于JVM中的程序檢測和修改運行時的行為。”這個概念常常會和內省(Introspection)混淆,以下是這兩個術語在Wikipedia中的解釋:
- 內省用于在運行時檢測某個對象的類型和其包含的屬性;
- 反射用于在運行時檢測和修改某個對象的結構及其行為。
- 從它們的定義可以看出,內省是反射的一個子集。有些語言支持內省,但并不支持反射,如C++。
內省示例:instanceof 運算符用于檢測某個對象是否屬于特定的類。
1
2
3
4
|
if (obj instanceof Dog) { Dog d = (Dog) obj; d.bark(); } |
反射示例:Class.forName()方法可以通過類或接口的名稱(一個字符串或完全限定名)來獲取對應的Class對象。forName方法會觸發類的初始化。
1
2
3
4
5
|
// 使用反射 Class<?> c = Class.forName( "classpath.and.classname" ); Object dog = c.newInstance(); Method m = c.getDeclaredMethod( "bark" , new Class<?>[ 0 ]); m.invoke(dog); |
在Java中,反射更接近于內省,因為你無法改變一個對象的結構。雖然一些API可以用來修改方法和屬性的可見性,但并不能修改結構。
數組的反射
數組的反射有什么用呢?何時需要使用數組的反射呢?先來看下下面的代碼:
1
2
3
4
5
6
|
Integer[] nums = { 1 , 2 , 3 , 4 }; Object[] objs = nums; //這里能自動的將Integer[]轉成Object[] Object obj = nums; //Integer[]當然是一個Object int [] ids = { 1 , 2 , 3 , 4 }; //Object[] objs2 = ids; //這里不能將int[]轉換成Object[] Object obj2 = ids; //int[] 是一個Object |
上面的例子表明:基本類型的一維數組只能當做Object,而不能當作Object[]。
1
2
3
4
5
6
7
8
|
int [][] intArray = {{ 1 , 2 }, { 3 , 4 }}; Object[] oa = intArray; Object obj = intArray; //Integer[][] integerArray = intArray; int[][] 不是 Integer[][] Integer[][] integerArray2 = new Integer[][]{{ 1 , 2 }, { 3 , 4 }}; Object[][] oa2 = integerArray2; Object[] oa3 = integerArray2; Object obj2 = integerArray2; |
從上面的例子可以看出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
|
package cn.zq.array.reflect; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Random; public class ArrayReflect { public static void main(String[] args) { Random rand = new Random( 47 ); int [] is = new int [ 10 ]; for ( int i = 0 ; i < is.length; i++) { is[i] = rand.nextInt( 100 ); } System.out.println(is); System.out.println(Arrays.asList(is)); /*以上的2個輸出都是輸出類似"[[I@14318bb]"的字符串, 不能顯示數組內存放的內容,當然我們采用遍歷的方式來輸出數組內的內容*/ System.out.println( "--1.通過常規方式遍歷數組對數組進行打印--" ); for ( int i = 0 ; i < is.length; i++) { System.out.print(is[i] + " " ); } System.out.println(); System.out.println( "--2.通過數組反射的方式遍歷數組對數組進行打印--" ); Object obj = is; //將一維的int數組向上轉為Object System.out.println( "obj isArray:" + obj.getClass().isArray()); for ( int i = 0 ; i < Array.getLength(obj); i++) { int num = Array.getInt(obj, i); //也能通過這個常用的方法來獲取對應索引位置的值 //Object value = Array.get(obj, i); //如果數組存放的是基本類型,那么返回的是基本類型對應的包裝類型 System.out.print(num + " " ); } } } |
輸出:
1
2
3
4
5
6
7
|
[I@14318bb [[I@14318bb] --1.通過常規方式遍歷數組對數組進行打印-- 58 55 93 61 61 29 68 0 22 7 --2.通過數組反射的方式遍歷數組對數組進行打印-- obj isArray:true 58 55 93 61 61 29 68 0 22 7 |
上面的例子首先創建了一個int的一維數組,然后隨機的像里面填充0~100的整數,接著通過System.out.println()方法直接對數組輸出或者用Arrays.asList方法(如果不是基本類型的一維數組此方法能按照期望轉成List,如果是二維數組也不能按照我們期望轉成List)將數組轉成List再輸出,通過都不是我們期望的輸出結果。接下來以常規的數組的遍歷方式來輸出數組內的內容,然后將int[]看成是一個Object,利用反射來遍歷其內容。Class.isArray()可以用來判斷是對象是否為一個數組,假如是一個數組,那么在通過java.lang.reflect.Array這個對數組反射的工具類來獲取數組的相關信息,這個類通過了一些get方法,可以用來獲取數組的長度,各個版本的用來獲取基本類型的一維數組的對應索引的值,通用獲取值的方法get(Object array, int index),設置值的方法,還有2個用來創建數組實例的方法。通過數組反射工具類,可以很方便的利用數組反射寫出通用的代碼,而不用再去判斷給定的數組到底是那種基本類型的數組。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package cn.zq.array.reflect; import java.lang.reflect.Array; public class NewArrayInstance { public static void main(String[] args) { Object o = Array.newInstance( int . class , 20 ); int [] is = ( int []) o; System.out.println( "is.length = " + is.length); Object o2 = Array.newInstance( int . class , 10 , 8 ); int [][] iss = ( int [][]) o2; System.out.println( "iss.length = " + iss.length + ", iss[0].lenght = " + iss[ 0 ].length); } } is.length = 20 iss.length = 10 , iss[ 0 ].lenght = 8 |
Array總共通過了2個方法來創建數組
Object newInstance(Class<?> componentType, int length),根據提供的class來創建一個指定長度的數組,如果像上面那樣提供int.class,長度為10,相當于new int[10];
Object newInstance(Class<?> componentType, int... dimensions),根據提供的class和維度來創建數組,可變參數dimensions用來指定數組的每一維的長度,像上面的例子那樣相當于創建了一個new int[10][8]的二維數組,但是不能創建每一維長度都不同的多維數組。通過第一種創建數組的方法,可以像這樣創建數組Object o = Array.newInstance(int[].class, 20)可以用來創建二維數組,這里相當于Object o = new int[20][];
當然通過上面例子那樣來創建數組的用法是很少見的,其實也是多余的,為什么不直接通過new來創建數組呢?反射創建數組不僅速度沒有new快,而且寫的程序也不易讀,還不如new來的直接。事實上通過反射創建數組確實很少見,是有何種變態的需求需要用反射來創建數組呢!
由于前面對基本類型的數組進行輸出時遇到一些障礙,下面將利用數組反射來實現一個工具類來實現期望的輸出:
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
|
package cn.zq.util; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Array; public class Print { public static void print(Object obj) { print(obj, System.out); } public static void print(Object obj, PrintStream out) { out.println(getPrintString(obj)); } public static void println() { print(System.out); } public static void println(PrintStream out) { out.println(); } public static void printnb(Object obj) { printnb(obj, System.out); } public static void printnb(Object obj, PrintStream out) { out.print(getPrintString(obj)); } public static PrintStream format(String format, Object ... objects) { return format(System.out, format, objects); } public static PrintStream format(PrintStream out, String format, Object ... objects) { Object[] handleObjects = new Object[objects.length]; for ( int i = 0 ; i < objects.length; i++) { Object object = objects[i]; if (object == null || isPrimitiveWrapper(object)) { handleObjects[i] = object; } else { ByteArrayOutputStream bos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(bos); printnb(object, ps); ps.close(); handleObjects[i] = new String(bos.toByteArray()); } } out.format(format, handleObjects); return out; } /** * 判斷給定對象是否為基本類型的包裝類。 * @param o 給定的Object對象 * @return 如果是基本類型的包裝類,則返回是,否則返回否。 */ private static boolean isPrimitiveWrapper(Object o) { return o instanceof Void || o instanceof Boolean || o instanceof Character || o instanceof Byte || o instanceof Short || o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double; } public static String getPrintString(Object obj) { StringBuilder result = new StringBuilder(); if (obj != null && obj.getClass().isArray()) { result.append( "[" ); int len = Array.getLength(obj); for ( int i = 0 ; i < len; i++) { Object value = Array.get(obj, i); result.append(getPrintString(value)); if (i != len - 1 ) { result.append( ", " ); } } result.append( "]" ); } else { result.append(String.valueOf(obj)); } return result.toString(); } } |
上面的Print工具類提供了一些實用的進行輸出的靜態方法,并且提供了一些重載版本,可以根據個人的喜歡自己編寫一些重載的版本,支持基本類型的一維數組的打印以及多維數組的打印,看下下面的Print工具進行測試的示例:
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
|
package cn.zq.array.reflect; import static cn.zq.util.Print.print; import java.io.PrintStream; import static cn.zq.util.Print.*; public class PrintTest { static class Person { private static int counter; private final int id = counter ++; public String toString() { return getClass().getSimpleName() + id; } } public static void main(String[] args) throws Exception { print( "--打印非數組--" ); print( new Object()); print( "--打印基本類型的一維數組--" ); int [] is = new int []{ 1 , 22 , 31 , 44 , 21 , 33 , 65 }; print(is); print( "--打印基本類型的二維數組--" ); int [][] iss = new int [][]{ { 11 , 12 , 13 , 14 }, { 21 , 22 ,}, { 31 , 32 , 33 } }; print(iss); print( "--打印非基本類型的一維數組--" ); Person[] persons = new Person[ 10 ]; for ( int i = 0 ; i < persons.length; i++) { persons[i] = new Person(); } print(persons); print( "--打印非基本類型的二維數組--" ); Person[][] persons2 = new Person[][]{ { new Person()}, { new Person(), new Person()}, { new Person(), new Person(), new Person(),}, }; print(persons2); print( "--打印empty數組--" ); print( new int []{}); print( "--打印含有null值的數組--" ); Object[] objects = new Object[]{ new Person(), null , new Object(), new Integer( 100 ) }; print(objects); print( "--打印特殊情況的二維數組--" ); Object[][] objects2 = new Object[ 3 ][]; objects2[ 0 ] = new Object[]{}; objects2[ 2 ] = objects; print(objects2); print( "--將一維數組的結果輸出到文件--" ); PrintStream out = new PrintStream( "out.c" ); try { print(iss, out); } finally { out.close(); } print( "--格式化輸出--" ); format( "%-6d%s %B %s" , 10086 , "is" , true , iss); /** * 上面列出了一些Print工具類的一些常用的方法, * 還有一些未列出的方法,請自行查看。 */ } } |
輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
--打印非數組-- java.lang.Object@61de33 --打印基本類型的一維數組-- [1, 22, 31, 44, 21, 33, 65] --打印基本類型的二維數組-- [[11, 12, 13, 14], [21, 22], [31, 32, 33]] --打印非基本類型的一維數組-- [Person0, Person1, Person2, Person3, Person4, Person5, Person6, Person7, Person8, Person9] --打印非基本類型的二維數組-- [[Person10], [Person11, Person12], [Person13, Person14, Person15]] --打印empty數組-- [] --打印含有null值的數組-- [Person16, null, java.lang.Object@ca0b6, 100] --打印特殊情況的二維數組-- [[], null, [Person16, null, java.lang.Object@ca0b6, 100]] --將一維數組的結果輸出到文件-- --格式化輸出-- 10086 is TRUE [[11, 12, 13, 14], [21, 22], [31, 32, 33]] |
輸出文件:
可見Print工具類已經具備打印基本類型的一維數組以及多維數組的能力了,總體來說上面的工具類還是挺實用的,免得每次想要看數組里面的內容都有手動的去編寫代碼,那樣是在是太麻煩了,以后直接把Print工具類拿過去用就行了,多么的方便啊。
上面的工具類確實能很好的工作,但是假如有這樣一個需求:給你一個數組(也有可能是其他的容器),你給我整出一個List。那么我們應該怎樣做呢?事實上Arrays.asList不總是能得到我們所期望的結果,java5雖然添加了泛型,但是是有限制的,并不能像c++的模板那樣通用,正是因為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
|
package cn.zq.util; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; public class CollectionUtils { public static List<?> asList(Object obj) { return convertToList( makeIterator(obj)); } public static <T>List<T> convertToList(Iterator<T> iterator) { if (iterator == null ) { return null ; } List<T> list = new ArrayList<T>(); while (iterator.hasNext()) { list.add(iterator.next()); } return list; } @SuppressWarnings ({ "rawtypes" , "unchecked" }) public static Iterator<?> makeIterator(Object obj) { if (obj instanceof Iterator) { return (Iterator<?>) obj; } if (obj == null ) { return null ; } if (obj instanceof Map) { obj = ((Map<?, ?>)obj).entrySet(); } Iterator<?> iterator = null ; if (obj instanceof Iterable) { iterator = ((Iterable<?>)obj).iterator(); } else if (obj.getClass().isArray()) { //Object[] objs = (Object[]) obj; //原始類型的一位數組不能這樣轉換 ArrayList list = new ArrayList(Array.getLength(obj)); for ( int i = 0 ; i < Array.getLength(obj); i++) { list.add(Array.get(obj, i)); } iterator = list.iterator(); } else if (obj instanceof Enumeration) { iterator = new EnumerationIterator((Enumeration) obj); } else { iterator = Arrays.asList(obj).iterator(); } return iterator; } public static class EnumerationIterator<T> implements Iterator<T> { private Enumeration<T> enumeration; public EnumerationIterator(Enumeration<T> enumeration) { this .enumeration = enumeration; } public boolean hasNext() { return enumeration.hasMoreElements(); } public T next() { return enumeration.nextElement(); } public void remove() { throw new UnsupportedOperationException(); } } } |
測試代碼:
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
|
package cn.zq.array.reflect; import java.util.Iterator; import java.util.List; import cn.zq.array.reflect.PrintTest.Person; import cn.zq.util.CollectionUtils; public class CollectionUtilsTest { public static void main(String[] args) { System.out.println( "--基本類型一維數組--" ); int [] nums = { 1 , 3 , 5 , 7 , 9 }; List<?> list = CollectionUtils.asList(nums); System.out.println(list); System.out.println( "--非基本類型一維數組--" ); Person[] persons = new Person[]{ new Person(), new Person(), new Person(), }; List<Person> personList = (List<Person>) CollectionUtils.asList(persons); System.out.println(personList); System.out.println( "--Iterator--" ); Iterator<Person> iterator = personList.iterator(); List<Person> personList2 = (List<Person>) CollectionUtils.asList(iterator); System.out.println(personList2); } } |
輸出:
1
2
3
4
5
6
|
--基本類型一維數組-- [1, 3, 5, 7, 9] --非基本類型一維數組-- [Person0, Person1, Person2] --Iterator-- [Person0, Person1, Person2] |
在java的容器類庫中可以分為Collection,Map,數組,由于Iterator(以及早期的遺留接口Enumeration)是所有容器的通用接口并且Collection接口從Iterable(該接口的iterator將返回一個Iterator),所以在makeIterator方法中對這些情形進行了一一的處理,對Map類型,只需要調用其entrySet()方法,對于實現了Iterable接口的類(Collection包含在內),調用iterator()直接得到Iterator對象,對于Enumeration類型,利用適配器EnumerationIterator進行適配,對于數組,利用數組反射遍歷數組放入ArrayList中,對于其他的類型調用Arrays.asList()方法創建一個List。CollectionUtils還提供了一些其他的方法來進行轉換,可以根據需要添加自己需要的方法。
總結:數組的反射對于那些可能出現數組的設計中提供更方便、更靈活的方法,以免寫那些比較麻煩的判斷語句,這種靈活性付出的就是性能的代價,對于那些根本不需要數組反射的情況下用數組的反射實在是不應該。是否使用數組的反射,在實際的開發中仁者見仁智者見智,根據需要來選擇是否使用數組的反射,最好的方式就是用實踐來探路,先按照自己想到的方式去寫,在實踐中不斷的完善。