JAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性,方法,泛型類型;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。java
JAVA反射(放射)機制:「程序運行時,容許改變程序結構或變量類型,這種語言稱爲動態語言」。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言。可是JAVA有着一個很是突出的動態相關機制:Reflection,用在Java身上指的是咱們能夠於運行時加載、探知、使用編譯期間徹底未知的classes。換句話說,Java程序能夠加載一個運行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其對象實體、或對其fields設值、或喚起其methods。python
在java中,一個類要想使用必須通過類的加載,鏈接和初始化這3個操做,正常狀況下這個3個步驟都有jvm來完成。c++
類的加載:類的加載是指定將類的class文件讀入內存,併爲之生成一個java.lang.Class對象,程序使用的每一個類都會在內存中爲之生成一個java.lang.Class對象。程序員
除了系統向咱們提供的類加載器外,咱們還能夠自定義咱們本身的類加載器,自定義類加載器用的很少spring
在靜態代碼塊(static{})中對Field初始化賦值bootstrap
ps:動態代碼塊({})中的代碼塊在這個階段不會本執行,它是在生成類的實例的時候執行,至關於構造方法中的語句,用的很少api
用圖畫出類初始化過程以下:數組
這裏對上面的這個圖作個簡要說明,當要初始化的類有父類時,先初始化其直接父類,初始化其直接父類的時候又會判斷其直接父類是否有父類,而後依次遞歸,直到要初始化的類的全部的父類都被初始化後在初始化最初要初始化的類安全
ps:有初始化語句指的的成員變量直接賦值,及靜態代碼塊等等,當建立某個類的實例,調用了某個類的靜態方法,訪問某個類或接口的Field或者爲之賦值,該類的子類被初始化等等都會致使類初始化ruby
前面說了類的加載器有不少,系統爲咱們提供了四種經常使用的類加載器,下面咱們看看這四種經常使用的類加載器各自的分工及如何協調工做的
運行以下代碼:
/** * 根類加載器bootstrapClassLoader,不是ClassLoader的子類,是由JVM自身實現的 * @author lt * */ public class BootstrapTest { public static void main(String[] args) { // 獲取根類加載器所加載的全部url數組 URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs(); //輸出根類加載器加載的所有的url for(int i = 0;i<urLs.length;i++){ System.out.println(urLs[i].toExternalForm()); } } }
控制檯輸出:
file:/D:/Program%20Files/Java/jre7/lib/resources.jar file:/D:/Program%20Files/Java/jre7/lib/rt.jar file:/D:/Program%20Files/Java/jre7/lib/sunrsasign.jar file:/D:/Program%20Files/Java/jre7/lib/jsse.jar file:/D:/Program%20Files/Java/jre7/lib/jce.jar file:/D:/Program%20Files/Java/jre7/lib/charsets.jar file:/D:/Program%20Files/Java/jre7/lib/jfr.jar file:/D:/Program%20Files/Java/jre7/classes
咱們能夠看到根類加載器所要加載的全部url
Extension ClassLoader(擴展類加載器):
負責加載JRE的擴展目錄(%JAVA_HOME%/jre/lib/ext和有java.ext.dirs系統屬性指定的目錄)中jar包的類
Application ClassLoader:
系統類加載器,負責加載程序員本身寫的類
其餘類加載器:
加載本身寫的類,如:URLClassLoader既能夠加載本地的類也能夠加載遠程的類
說明:
自定義類加載器很是簡單,前面說了java類加載器中除了根類加載器不是ClassLoader外,其餘類加載器都是ClassLoader的子類,ClassLoader中有大量的protected的方法,毫無疑問,咱們能夠經過繼承ClassLoader來擴展類加載器,從而實現咱們本身的類加載器。
一般咱們若是重寫loadClass方法時使自定義ClassLoader變得困難,咱們還得本身實現該類的父類委託,緩衝機制兩種策略,而loadClass的執行過程以下:
findLoader(String)
來檢查該類是否已經加載,若是加載了則直接返回該類的Class對象,不然返回NullloadClass()
方法,若是父類加載器爲Null,則使用根類加載器咱們能夠看到前面兩步是實現父類的一些邏輯,其實這裏兩步實現了父類委託和緩衝機制策略,而咱們只須要從新findClass(String)
方法來實現咱們本身的邏輯便可,這樣使自定義類加載器就簡單多了
前面說了類的加載器,下面開始真正學習java反射機制,java反射機制能夠時咱們在運行時刻獲取類的信息,如:類的成員變量類型,值,方法的信息及調用方法,獲取泛型類型,獲取註解等等。
java反射的相關api都在java.lang.reflect
包下:學習api最好的方法仍是看官網文檔,由於官方文檔最權威。下面咱們經過一個測試類來學習反射的最經常使用的知識。
// 經過Class的靜態方法forName加載類,該方法會初始化類 Class clazz = Class.forName("Common"); // 經過反射生成該類的實例,調用public的無參構造方法,反射生成類的實例,該類必須得有一個public的無參方法 Object newInstance = clazz.newInstance();
public static void testConstructor(Class clazz){ System.out.println("-----------構造方法測試-----------"); // 獲取因此的構造方法 Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for(int i=0;i<declaredConstructors.length;i++){ int index = i+1; // 由於i從0開始 String str = "第"+index+"個構造方法權限修飾符爲"; if(declaredConstructors[i].getModifiers() == 1){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字 str+="public"; } if(declaredConstructors[i].getModifiers() == 2){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字 str+="private"; } if(declaredConstructors[i].getModifiers() == 4){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字 str+="protected"; } str+="名稱爲:"+declaredConstructors[i].getName(); System.out.println(str); } }
/** * 這個方法經過反射獲取了方法的相關信息,能夠看到咱們獲得了方法 * 的所有信息 * 這裏字符串拼接比較多,因爲是測試,因此就沒用StringBuffer拼接 * @param clazz */ public static void testCommonMethodInfo(Class clazz){ System.out.println("-----------普通方法測試-----------"); Method[] methods = clazz.getMethods(); System.out.println(methods.length); for(int i=0;i<methods.length;i++){ int dexI = i+1; String str = "第"+dexI+"個方法形式爲:"; // 獲取方法權限修飾符 int modifiers = methods[i].getModifiers(); if(modifiers == 1){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字 str+="public"; } if(modifiers == 2){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字 str+="private"; } if(modifiers == 4){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字 str+="protected"; } String returnType = methods[i].getReturnType().getSimpleName(); // 獲取方法返回類型 str+=" "+returnType+" "; String name = methods[i].getName(); // 獲取方法名稱 str+= name; str+="("; // 獲取方法參數的類型 Class[] parameterTypes = methods[i].getParameterTypes(); for(int j=0;i<parameterTypes.length;i++){ str+=parameterTypes[j]+" "; } str+=")"; System.out.println(str); } }
/** * 經過反射調用方法 * @param clazz * @throws SecurityException * @throws NoSuchMethodException * @throws InvocationTargetException * @throws IllegalArgumentException * @throws IllegalAccessException */ public static void testMethodInvoke(Class clazz,Object newInstance) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ System.out.println("-----------測試方法的調用-----------"); /** * 因爲咱們知道類中有這種類型的方法,因此咱們直接指定要獲取的方法類型 * 若是不知道咱們要調用的 方法的類型,咱們能夠獲取方法的參數類型等等全部信息 * 而後匹配到咱們須要調用的方法,這裏作個說明,咱們要調用的方法名是知道的 */ Method sum = clazz.getMethod("sum",int.class,int.class); System.out.println(sum); sum.invoke(newInstance, 1,1); Method setS = clazz.getMethod("setS",String.class); System.out.println(setS); setS.invoke(newInstance, "賦值啦"); }
/** * 測試類的Field * @param clazz * @throws IllegalAccessException * @throws IllegalArgumentException */ public static void testField(Class clazz,Object obj) throws IllegalArgumentException{ System.out.println("-----------測試類的Field-----------"); // 獲取因此的Field,包括private修飾的,但不能直接獲取和直接改變private修飾的值 Field[] fields = clazz.getDeclaredFields(); for(int i=0;i<fields.length;i++){ int dexI = i+1; System.out.print("第"+dexI+"個name:"+fields[i].getName()+" "); Class type = fields[i].getType(); String typeStr = type.getName(); try{ if(typeStr.equals("int")){ System.out.println("value:"+fields[i].getInt(obj)); }else if(typeStr.equals("java.lang.String")){ // 字符串形式經過get(Object obj)方法取得,若是該Field的權限爲private,則 獲取值的時候會報java.lang.IllegalAccessException System.out.println("value:"+fields[i].get(obj)); } }catch(IllegalAccessException ex){ System.out.println("不能獲取private修飾的屬性值"); } } }
/** * 測試註解(類上的註解),屬性方法上的註解分別經過 * Field對象和Method對象的getAnnotations()方法能夠獲得 * 和這裏是同樣的 * @param clazz */ public static void testAnnotation(Class clazz){ System.out.println("-----------測試註解-----------"); Annotation[] annotations = clazz.getAnnotations(); for(int i=0;i<annotations.length;i++){ System.out.println(annotations[i]); } }
/** * 泛型測試 * @throws NoSuchFieldException */ private static void genericTest() throws NoSuchFieldException { System.out.println("-----------獲泛型測試-----------"); Class<ReflectTest> clazz = ReflectTest.class; Field f = clazz.getDeclaredField("map"); // 直接使用getType()只對普通類型有效,並不能獲得有泛型的類型 Class<?> type = f.getType(); // 下面的代碼能夠看到只輸出了java.util.Map System.out.println(" map 的類型爲:"+type); // 獲取Field實例f的泛型類型 Type genericType = f.getGenericType(); // 若是genericType是ParameterizedType對象 if(genericType instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) genericType; // 獲取原始類型 Type rawType = parameterizedType.getRawType(); System.out.println("原始類型是:"+rawType); // 取得泛型類型的泛型參數 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for(int i=0;i<actualTypeArguments.length;i++){ System.out.println("第"+i+"個泛型類型爲:"+actualTypeArguments[i]); } }else{ System.out.println("泛型類型獲取出錯"); } }
/** * 獲取泛型T的實際類型 */ public static void getTType(){ System.out.println("-----------獲取泛型T的實際類型-----------"); Dog dog = new DogImpl(); // 注意,下面這種寫法不能獲取到T的實際類型,由於範式要在編譯的時候就指定類型,在運行時候指定類型是獲取不到真實類型的 // Dog<Cat> dog = new Dog<Cat>(); }
因爲註釋說了很清楚了,因此這裏就不過多介紹了,以上這些方法都是參考了api提供的一些方法本身寫的一些測試,其實都只是一個簡單方法的使用,但這些方法是反射中最基本最經常使用的方法,api方法不少,因此咱們學習api的時候最好時刻查詢文檔,查看文檔是個好習慣。