1 反射的基礎Class類
Person類代表人,它的實例對象就是張三,李四這樣一個個具體的人。
在Java程序中的各個Java類屬於同一類事物,描述這類事物的Java類名就是Class。
Class類代表Java類,它的各個實例對象又分別對應什麼呢?
對應各個類在內存中的字節碼。例如,Person類的字節碼,ArrayList類的字節碼等等。
一個類被類加載器加載到內存中,佔用一片存儲空間,這個空間裏面的內容就是類的字節碼,不同的類的字節碼是不同的,所以它們在內存中的內容是不同的,這一個個的空間可分別用一個個的對象來表示,這些對象顯然具有相同的類型,這個類型就是Class。
Class類描述了哪些方面的信息呢:類的名字,類的訪問屬性,類所屬於的包名,字段名稱的列表、方法名稱的列表,等等。
學習反射,首先就要明白Class這個類。
如何得到各個字節碼對應的實例對象(Class類型):
1,類名.class,例如,System.class。
2,對象.getClass(),例如,new Date().getClass()。
3,Class.forName("類名"),例如,Class.forName("java.util.Date");。
九個預定義Class實例對象(八大基本類型+void):
Byte.TYPE,Integer.TYPE,Short.TYPE,Long.TYPE,Float.TYPE,Double.TYPE,Character.TYPE,Boolean.TYPE,Void.TYPE。(返回對應的基本數據類型的字節碼Class對象)
這些對象通過基本數據類型調用,被public static final修飾。
這些對象僅能通過下列聲明爲public static final的變量訪問。
注意:
1、Int.class == Integer.TYPE。
2、數組類型的Class實例對象Class.isArray()爲true。
總之,只要是在源程序中出現的類型,都有各自的Class實例對象,例如,int[],void…
示例:
2 反射的概念
反射就是把Java類中的各種成分映射成相應的java類。
例如,一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示。就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。
表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Contructor、Package等等。
一個類中的每個成員都可以用相應的反射API類的一個實例對象來表示,通過調用Class類的方法可以得到這些實例對象。
3 構造方法的反射Constructor類
Constructor類代表某個類中的一個構造方法。
Constructor對象代表一個構造方法,Constructor對象上會有的方法有:得到方法名字,得到所屬於的類,產生實例對象。
得到某個類所有的構造方法:
例子:Constructor[] constructors= Class.forName("java.lang.String").getConstructors();
得到某一個構造方法:
例子:Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class); //獲得方法時要用到類型
注意:
一個類有多個構造方法,根據getConstructors() 中參數的個數和類型,區分清楚想得到其中的哪個方法
例如,Class.getMethod(name,Class... args)中的args參數就代表所要獲取的那個方法的各個參數的類型的列表。
重點:getConstructors() 中參數類型用什麼方式表示?用Class實例對象。
重點:getConstructors()只能獲得被public修飾的構造函數。沒有public修飾的可以通過getDeclaredConstructor()獲得。
創建實例對象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));
//調用獲得的方法時要用到上面相同類型的實例對象
Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance(); //該方法內部先得到默認的構造方法,然後用該構造方法創建實例對象。
代碼示例:
4 成員變量的反射(暴力反射)
Field類代表某個類中的一個成員變量。
問題:
得到的Field對象是對應到類上面的成員變量,還是對應到對象上的成員變量?
類只有一個,而該類的實例對象有多個,如果是與對象關聯,哪關聯的是哪個對象呢?
所以字段field X 代表的是X的定義,而不是具體的X變量。
getField():獲取公有字段的Class實例。
getDeclaredField():獲取私有字段的Class實例。
fieldX.setAccessible( true):可以使對象rp中的私有成員變量X,可以從外部被訪問,即fieldX.get( rp),這稱爲暴力反射。
代碼示例:
暴力反射:
通過調用字段fieldX的setAccessible( true) 方法,可以使對象rp中的私有成員變量X,可以從外部被訪問,即fieldX.get( rp),這稱爲暴力反射。
5 成員方法的反射
Method類代表某個類中的一個成員方法。
得到類中的某一個方法:
例子:Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
調用方法:
通常方式:char x = str.charAt(1));
反射方式:char x = charAt.invoke(str, 1)); //調用charAt實例代表的方法,調用者是str字符串,傳遞參數1。
注意:
如果傳遞給Method對象的invoke()方法的第一個參數爲null,說明該Method對象對應的是一個靜態方法!
jdk1.4和jdk1.5的invoke方法的區別:
Jdk1.4:public Object invoke(Object obj,Object[] args)
Jdk1.5:public Object invoke(Object obj,Object... args) //可變參數
代碼示例:
6 用反射方式執行某個類中的main方法
寫一個程序A,該程序能根據用戶提供的類名B,去執行B類中的main方法。
通過給A的main方法設置傳入的參數,來指定B類的類名。
命令行下給main方法傳遞參數:B類的類名(包括包路徑)在java運行命令之後。
Eclipse中給main方法傳遞參數:右擊Run As-->點擊Run Configurations-->點擊Arguments選項卡。
問題:
啓動Java程序的main方法的參數是一個字符串數組,
即public static void main(String[] args),
通過反射方式來調用這個main方法時,如何爲invoke方法傳遞參數呢?
按jdk1.5的語法,整個數組是一個參數,而按jdk1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作爲參數傳遞給invoke方法時,javac按jdk1.4的語法進行處理,即把數組拆開成若干個單獨的參數。
所以,在給main方法傳遞參數時,不能使用代碼mainMethod.invoke(null,new String[]{“xxx”});javac只把它當作jdk1.4的語法進行理解,即把數組拆開成若干個單獨的參數,而不把它當作jdk1.5的語法解釋(整個數組是一個參數),因此會出現參數類型不對的問題。
解決該問題的方法:
方法一:mainMethod.invoke(null, new Object[]{new String[]{"xxx"}}); //多加一個數組。
方法二:mainMethod.invoke(null, (Object)new String[]{"xxx"}); //(Object) 是告訴編譯器,傳遞的是一個對象不是一個數組,就不會把數組拆開成若干個單獨的參數了。
代碼示例:
命令行下編譯、運行結果:
7 數組的反射
7.1 數組的反射特點
具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象(此處比較與值無關)。
代表數組的Class實例對象的getSuperClass()方法,返回的父類爲Object類對應的Class。
基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;
非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。
代碼及註釋:
7.2 Arrays類的asList()方法處理int[]和String[]時的差異
JDK1.4中爲 Arrays.asList(Object[] a);
JDK1.5中爲 Arrays.asList(T... a);
所以 as.List( new int[]{1,2,3}) 中,數組元素的類型是int,而int不是Object,所以不能打印出數組內容。
代碼及註釋:
注意:Class類的getName方法返回值中[表示數組的深度,[表示一維數組,[[表示二維數組。剩下的內容則表示數組類型,例如I表示int類型。
7.3 Array類用於完成對數組的反射操作
反射中,Array類表示數組,位於java.lang.reflect 包中。
Array類對數組的反射操作:
8 反射的作用:實現框架功能
框架:
我做房子賣給用戶住,由用戶自己安裝門窗和空調,我做的房子就是框架,用戶需要使用我的框架,把門窗插入進我提供的框架中。
框架與工具類有區別,工具類被用戶的類調用,而框架則是調用用戶提供的類。
框架要解決的核心問題:
我在寫框架(房子)時,你這個用戶可能還在上小學,還不會寫程序呢?我寫的框架程序怎樣能調用到你以後寫的類(門窗)呢?
因爲在寫才程序時無法知道要被調用的類名,所以,在程序中無法直接new 某個類的實例對象了,而要用反射方式來做。
下面的代碼中實現一個簡單的框架,使用時,我們只需更改配置文件中的值,配置文件config.properties存放在當前目錄。
Config.properties文件中內容:
className1=java.util.HashSet
className2=java.util.ArrayList
代碼及註釋:
運行結果: