標籤:黑馬程式員 java反射 reflect class
——Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! ——
一、概述
Java 反射機制是在運行狀態中,對於程式中的任意一個類,通過反射機制都能夠知道這個類的所有屬性和方法,包括共有、包含、預設和私人。對於任意的一個對象,通過反射機制都可以去調用它的每一個方法,這種機制就稱為Java的反射機制。一般的操作都在java.lang.reflect包中,常用到的類有Constructor,Field和Method三種。既然是對Java類的反射,當然也有個比不可少的類Class,這也是一個類,作為Java中類檔案的一個類,擷取類的類一般有三種方式:
String s = "abc";// 通過類直接擷取Class<?> c1 = String.class;// 通過變數的getClass()方法擷取Class<?> c2 = s.getClass();// 通過Class的靜態方法forName(String)根據類的全名擷取Class<?> c3 = Class.forName("java.lang.String");
那麼Java的反射機制有什麼用處呢?一般的程式開發時,我們都是對使用的類直接操作,如建立等,但是如果遇到不能直接操作時怎麼辦呢,這裡是反射的一個用處,如可是根據一個類名稱的字串來擷取類的執行個體,這也是一種用法,程式可以根據設定檔內的類名去建立不同的類。還有一些情況如,類中有一些私人方法,我們想要訪問或者修改,正常的做法是行不通的,通過反射則可以達到目的,這也成為暴力反射。
二、
Class類
第一次見到這個類可能會有些疑惑,Java中的類有很多,用過的也有很多,但是第一次見到這個描述類的類,多少還是有點好奇的,這也更加驗證了一點,所有事物都可以被描述成對象,既然類這麼常見,那當然也不會例外。首先我們應該對類檔案有一些初步瞭解,我們在編寫代碼之後使用javac進行編譯,便可以看到目錄下由.java檔案產生的.class檔案,那麼這些.class檔案便是類檔案。執行這些檔案時,如java x.class,類檔案便會被載入到記憶體中,以便使用,類檔案中使用到的其他類也會隨之載入,通過下面的例子結果可以說明一個問題,便是類檔案在記憶體中是唯一的。
String a1 = "abc";System.out.println(a1.getClass() == String.class);System.out.println(a1.getClass() == Class.forName("java.lang.String"));// 執行結果為truetrue
對於基礎資料型別 (Elementary Data Type)boolean,char,byte,short,int,long,float,double共8個和一個void都有其對應的類,如int.class,注意int.class和Integer.class不是同一個類。對於數組也有其對應的類,如int[].class,int[][].class,類型相同,維數相同的數組類才是同一個類,即int[].class != int[][].class,其他類似,數組類的類名有一定的規則,如int[][].class.getName()返回的是[[I,其中[的個數表示維數,int對應的是I,對應關係如下:
| 類型名 |
對應的內容 |
| boolean |
Z |
| byte |
B |
| char |
C |
| double |
D |
| float |
F |
| int |
I |
| long |
J |
| short |
S |
| class or interface |
Lclassname |
數組類也是繼承自Object的,如可以用Object對象接收int[],如Object obj = new int[]{1, 2, 3};,但是注意一點Object[] obj = new int[]{1, 2, 3};這種寫法是不正確的,因為基礎資料型別 (Elementary Data Type)不能轉成Object對象。
Class的常用方法
getConstructor(Class<?>... parameterTypes)此方法用於擷取類的構造方法,其中的參數是可變參數,用於指定擷取的構造方法是那個,如擷取String類的String(StringBuilder stringBuilder)構造方法,可以用Constructor cons String.class.getConstructor(StringBuilder.class);方式來擷取。這個方法類似的另一種擷取多構造方法的是getConstructors(),可以擷取所有的構造方法,返回一個Constructor的數組。
newInstance()方法用於執行個體化一個對象,即用這個類建立一個對象。範例程式碼如下:
import java.lang.reflect.*;class Main { public static void main(String[] args) throws Exception { // 擷取String的一個構造方法 Constructor cons = String.class.getConstructor(StringBuilder.class); System.out.println(cons); // 建立一個空的字串 String s = String.class.newInstance(); System.out.println(s); }}// 執行結果為public java.lang.String(java.lang.StringBuilder)[空串]
String getName()用於擷取類名(包含包名),如String.class.getName()的傳回值為"java.lang.String"。
Package getPackage()用於返回包名。
Field getField(String name)擷取類的成員變數。
Field[] getFields()擷取類發所有可訪問的成員變數。
Field getDeclaredField(String name)根據名稱擷取類聲明的一個成員變數。這裡可以擷取private修飾的成員變數,同樣的還有 Field[] getDeclaredFields()用於擷取所有類聲明的成員變數。
Method getMethod(String name, Class<?>... parameterTypes)擷取方法,和擷取構造方法類似,同樣也有擷取所有方法和擷取聲明的方法等等。
三、
Constructor
Class<T> getDeclaringClass()返回 Class 對象,該對象表示聲明由此 Constructor 對象表示的構造方法的類。
int getModifiers()返回以整數形式返回此 Constructor 對象所表示構造方法的 Java 語言修飾符。
T newInstance(Object... initargs)使用此 Constructor 對象表示的構造方法來建立該構造方法的聲明類的新執行個體,並用指定的初始化參數初始化該執行個體。
import java.lang.reflect.*;class Main { public static void main(String[] args) throws Exception { // 擷取String的一個構造方法 Constructor cons = String.class.getConstructor(StringBuilder.class); // 使用擷取的建構函式樣本化一個對象 String s = (String)cons.newInstance(new StringBuilder("abc")); // 輸出這個對象 System.out.println(s); // 擷取建構函式對應的類 Class<?> c = cons.getDeclaringClass(); // 輸出類名 System.out.println(c.getName()); // 構造方法的類型 System.out.println(Member.DECLARED == cons.getModifiers()); }}
四、
Field
描述一個類的成員,一般功能有擷取這個成員的值,設定這個成員的值,擷取這個成員的資料類型等,下面通過一個執行個體示範一下Field的相關操作,執行個體內容為將一個自訂對象中的成員(String類型)中出現的疊詞替換為[double],首先是擷取該自訂對象的類對象,然會擷取所有聲明的成員,然會擷取這些成員的值,最後再將替換過的值設定回去。
import java.lang.reflect.*;/** * 將一個自訂類中的成員變數(String類型)中出現的連續兩 * 個相同的字元替換成[double]。如aa變成[double]。 */class Main { public static void main(String[] args) throws Exception { Demo demo = new Demo(); // 擷取demo對象中的聲明的成員 Field[] fields = demo.getClass().getDeclaredFields(); for(Field field : fields) { // 如果不是可直接存取的,則將其修改為可直接存取型 if(!field.isAccessible()) { field.setAccessible(true); } // 擷取原始字串內容 String oldString = (String)field.get(demo); // 將疊詞替換為[double] String newString = oldString.replaceAll("(.)\\1", "[double]"); // 最後將新的字串設定回去 field.set(demo, newString); // 輸出新的變數內容 System.out.println(field); } System.out.println(demo.toString()); }}class Demo { // 修改後應為ac[double]de public String a = "abccde"; // 修改後應為he[double]oawdcs private String b = "helloawdcs"; // 修改後應為[double]sdn[double]asdnjasd[double]adas protected String c = "aasdnjjasdnjasdbbadas"; public String toString() { return a + "/" + b + "/" + c; }}
五、
Method
描述一個類的方法,一般常用功能, Class<?> getReturnType() 返回一個 Class 對象,該對象描述了此 Method 對象所表示的方法的正式傳回型別。Object invoke(Object obj, Object... args)對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法。 下面通過一個執行個體,內容是通過反射功能擷取一個String對象的第一個位置的字元,即調用charAt()方法。
import java.lang.reflect.*;class Main { public static void main(String[] args) throws Exception { String s = "abc"; // 擷取String的類對象 Class<?> c = String.class; // 擷取String類的charAt方法 Method method = c.getMethod("charAt", int.class); // 通過方法的反射擷取第1個位置的字元 char ch = (char)method.invoke(s, 1); System.out.println(ch); // 結果為b }}
六、數組的反射
使用反射傳遞數組參數時,需要注意其可能會被系統自動拆分為對應的可變參數類型,如main方法的String[]會變成String...,所以傳遞時一般有兩種解決方式,一是將其再次封裝成一個數組,如new Object[]{new String[]{"abc", "cba", "bac"}};二是將其強轉為父類(Object)new String[]{"abc", "cba", "bac"},兩種方式都可。
import java.lang.reflect.*;/** * 通過反射調用另一個類的main方法,傳遞去一個數組參數 */class Main { public static void main(String[] args) throws Exception { // 首先擷取對應類的Class對象 Class clazz = Demo.class; // 擷取Demo的main方法 Method method = clazz.getMethod("main", String[].class); // 定義待傳遞的參數 String[] strs = new String[]{"Hello", "Hi", "Bye"}; /*調用靜態方法時,不需要傳遞變數 *注意:因為JDK的版本,如果直接傳遞strs會被拆分成 *可變類型String...,不符合main的參數要求,所以 *這裡將其轉為一個整體,Object類型 */ method.invoke(null, (Object)strs); }}class Demo { public static void main(String[] args) { // 將傳遞進來的參數輸出 for(String s : args) { System.out.println("參數:" + s); } }}// 執行結果參數:Hello參數:Hi參數:Bye
前面已經說過數組也是一個類,如int[].class,這種類和其他類有一些區別,Java提供了一個用於運算元組類的類Array,Array 類提供了動態建立和訪問 Java 數組的方法。Array 允許在執行 get 或 set 操作期間進行擴充轉換,但如果發生收縮轉換,則拋出 IllegalArgumentException。範例程式碼如下:
import java.util.*;import java.lang.reflect.*;class Main { public static void main(String[] args) throws Exception { // 擷取String[]對應的類 Class<?> clazz = String[].class; // 定義一個String數組並初始化 String[] strs = new String[]{"Hello", "Hi", "Bye"}; // 如果這個類是數組類 if(clazz.isArray()) { // 擷取數組的長度 int len = Array.getLength(strs); // 變數這個數組 for(int i=0; i<len; i++) { // 反射擷取strs的第i位置的值 String s = (String)Array.get(strs, i); // 輸出擷取的值 System.out.println(i+":"+s); // 如果這個值為Hi,則將其改為Good if(s.equals("Hi")) { Array.set(strs, i, "Good"); } } // 將整個數組輸出 System.out.println(Arrays.toString(strs)); } }}// 執行結果為0:Hello1:Hi2:Bye[Hello, Good, Bye]
七、反射的應用
在實際開發中,反射的應用一般是用於開發架構,架構是一些功能的核心抽取,涵蓋了一個系統的整體,但是沒有完成細節的東西。
下面是一個架構的執行個體,程式可以根據使用者配置的檔案,執行中使用不同的類,在目前的目錄建立一個檔案名稱為config.properties在其內寫入className=java.util.ArrayList,這是程式中的集合使用的是ArrayList,最後的結果是集合大小為4,然後將className的值修改為java.util.HashSet,最後的結果則變為了3,如此一來,程式可以根據不同的配置使用不同的類,架構便是如此,在使用的類不確定,或者沒有現成的類可使用時,不防使用反射來實現程式功能。代碼如下:
import java.io.*;import java.util.*;import java.lang.reflect.*;class Main { public static void main(String[] args) throws Exception { // 配置 Properties properties = new Properties(); // 通過類載入器擷取輸入資料流 InputStream in = Main.class.getResourceAsStream("config.properties"); // 從流中載入配置 properties.load(in); // 關閉流 in.close(); // 擷取類名 String className = properties.getProperty("className"); // 根據類名擷取類 Class clazz = Class.forName(className); // 根據clazz類,建立一個集合對象 Collection collection = (Collection) clazz.newInstance(); // 向集合中添加一些字串 collection.add(new String("Hello")); collection.add(new String("Hi")); collection.add(new String("Bye")); collection.add(new String("Bye")); // 輸出集合的大小 System.out.println(collection.size()); }}
黑馬程式員-Java 反射