1,概念:java
所謂的反射,能夠理解爲在運行時獲取對象類型信息的操做。
2,說明:數組
1)Class對象是在加載類時由虛擬機以及經過調用類加載器中的defineClass方法自動構造的。 2)一個類被類加載器加載到內存中,佔用一片存儲空間,這個空間裏面的內容就是類的字節碼,一個類在虛擬機中只有一份字節碼
3,得到 Class 對象的三種方法:緩存
1)調用Objec類的getClass()方法來獲得Class對象,這也是最多見的產生Class對象的方法。 MyObject mobj = new MyObject(); Class c1 = mobj.getClass(); 2)使用Class類的forName(String className)靜態方法,className表示全限定名;如String的全限定名:java.lang.String; Class c2 = Class.forName("java.lang.String"); 3)調用某個類的class屬性,即便用「類型名.class」來獲取該類型對應的Class對象。注:基本類型和引用類型都有class屬性 引用類型:Class c1 = MyType.class; 基本類型:Class c2 = int.class; 數組:Class c3 = double[].class; 注意: 基本類型(boolean、byte、char、short、int、long、float和double)以及關鍵字void經過調用本身的class屬性能夠獲得其Class對象 說明:基本類型的包轉類和Void類都有靜態的TYPE字段,該字段返回的是他們基本類型的Class對象! 即:Integer.TYPE == int.class; 注意:int和Integer表示不一樣的數據類型,故int.class與Integer.class是不一樣的Class對象,因此Integer.TYPE與Integer.class也是不一樣的Class對象。 數組:全部具備相同元素類型和維數的數組都共享同一個Class對象,和數組元素長度、元素的值以及元素的順序無關
4,Class類中的方法:安全
得到實現的接口: public Class<?>[] getInterfaces() 肯定此對象所表示的類或接口實現的接口 得到構造方法: public Constructor<T> getConstructor(Class<?>... parameterTypes) 得到Class所表示類中用public修飾的指定構造器,注:當沒有傳入參數時,即 getConstructor() 返回的是默認的構造器 parameterTypes:是按聲明順序標識該方法形參類型的Class對象的一個數組 public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 得到Class所表示類中指定的構造器,和訪問權限無關 public Constructor<?>[] getConstructors() 得到Class所表示類中全部用public修飾的構造器 public Constructor<?>[] getDeclaredConstructors() 得到Class所表示類中全部的構造器,和訪問權限無關 注:一個類的默認構造器的訪問權限和類的訪問權限一致 得到普通方法: public Method getMethod(String name, Class<?>... parameterTypes) 得到Class所表示類中用public修飾的指定方法 name:用於指定所需方法的簡單名字 parameterTypes:是按聲明順序標識該方法形參類型的Class對象的一個數組 public Method[] getMethods() 得到Class所表示類中全部用public修飾的方法,包括繼承過來的方法 public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 得到Class所表示類中指定的方法,不包括繼承過來的方法 public Method[] getDeclaredMethods() 得到Class所表示類中全部的方法,不包括繼承過來的方法 得到字段: 與得到普通方法的方式如出一轍。 得到內部類: public Class<?>[] getClasses() 獲得Class所描述的類中全部用public修飾的內部類 public Class<?> getDeclaredClasses() 獲得Class所描述的類中全部的內部類
5,使用反射建立對象:優化
1)使用Class對象的newInstance方法: public T newInstance():建立此Class對象所表示的類的一個實例,至關於調用了表示類的默認構造方法 2)使用Class對象獲取指定的Constructor對象,再調用Constructor的newInstance()方法建立對象類的實例,即用指定的構造方法建立實例。 1,獲得一個指定的構造方法 2,調用構造方法(Constructor)對象的newInstance(Object... initargs)方法 public T newInstance(Object... initargs) initargs表示構造器須要傳入的實際參數
6,忽略訪問權限(暴力反射)code
使用java.lang.reflect.AccessibleObject類中的setAccessible(boolean flag)方法,當flag爲true的時候,就會忽略訪問權限(天然也可訪問到私有的成員) 注:Field、 Method、Constructor都繼承了AccessibleObject類
7,使用反射調用方法對象
說明:每一個Method的對象對應一個具體的底層方法。得到Method對象後,程序可使用Method裏面的invoke方法來執行該底層方法 public Object invoke(Object obj, Object... args):對帶有指定參數(args)的指定對象(obj)調用由此 Method 對象表示的底層方法 obj - 調用底層方法的對象 args - 用於方法調用的參數,通常使用 new Object[]{arg1,arg2} 的形式。 調用帶有可變參數的方法: 要點:把可變參數看成數組參數。 如:show(String... s)做爲show(String[] s)調用; show(T... a)做爲show(Object[] a)調用; 說明: 1)若可變參數元素類型是引用類型: JDK內部接收到參數以後,會自動拆包取出參數再分配給該底層方法,爲此咱們須要把這個數組實參先包裝成一個Object對象或者把實際參數做爲一個Object一維數組的元素再傳遞。 2)若可變參數元素類型是基本類型: JDK內部接收到參數以後,不會拆包,因此能夠沒必要再封裝,不過封裝了也不會錯 結論:無論基本類型仍是引用類型都使用Object[]{arg1,arg2}封裝一層,保證無誤 例子:使用反射執行Arrays類中的 public static <T> List<T> asList(T... a) {}方法(注:static後面的<T>:聲明使用T來做爲泛型) Class<Arrays> clazz = Arrays.class; // 把可變參數:T... a 看成數組參數:Object[] a Method m = clazz.getMethod("asList", Object[].class); Object value = m.invoke(null, new Object[]{new String[]{"1","2","3"}}); System.out.println(value); 注意: 1)若是底層方法是靜態的,那麼能夠忽略指定的obj參數,該參數能夠爲null 2)若是底層方法所需的形參數爲0,則所提供的args能夠爲:省略、null、或 new Object[]{} 3)返回值: 若底層方法的返回值是基本類型,則首先將其包裝成對象,而後返回該對象 若底層方法的返回類型爲void,則返回null 若底層方法返回的是數組對象,則方法返回該數組對象,注意:若是是基本類型的數組,數組元素沒有進行包裝,仍返回基本類型的數組對象 注:可使用java.lang.reflect.Array類的get方法,得到數組對象的值 public static Object get(Object array, int index) 說明:返回數組對象中指定索引元素的值。若是該值是一個基本類型的值,則自動將其包裝在一個對象中。 注意:若是指定的對象不是一個數組,將拋出IllegalArgumentException異常 舉例: for (int i = 0; i < Array.getLength(array); i++) { System.out.println(Array.get(array, i)); }
8,使用反射操做字段:繼承
Field提供兩組方法操做字段 得到字段對象(Field)的值 public Object get(Object obj):返回指定對象上此 Field 表示字段的值。若是該值是一個基本類型值,則自動將其包裝在一個對象中。 obj:字段所屬的對象,下同。 public xxx getXxx(Object obj):獲取obj對象該Field的字段值,此處的xxx表示8個基本數據類型。若該字段的類型是引用數據類型則使用:Object get(Object obj); 設置字段對象(Field)的值 public void set(Object obj, Object value):將指定對象變量上此 Field 對象表示的字段設置爲指定的新值。若是底層字段的類型爲基本類型,則對新值進行自動解包。 public void setXxx(Object obj,xxx val):將obj對象的該Field字段設置成val值,此處的xxx表示8個基本數據類型。若該字段的類型是引用數據類型則使用:void set(Object obj, Object value); 訪問靜態屬性: 獲取靜態屬性的值:field.get(null) 或 field.get(obj) 設置靜態屬性的值:field.set(null, string) 或 field.set(obj, string) Field提供的其餘經常使用方法 public String getName() 返回此 Field 對象表示的字段的名稱。 public Class<?> getType() 返回一個 Class 對象,它標識了此 Field 對象所表示字段的聲明類型 public Type getGenericType() 返回一個 Type 對象,它表示此 Field 對象所表示字段的聲明類型
9,反射的優化:索引
說明: 經過反射的方式建立對象 比 直接new對象 慢幾倍 經過反射的方式訪問對象的屬性 比 直接訪問對象的屬性 慢幾百倍 經過反射的方式調用對象的方法 比 直接調用對象的方法 慢幾百倍 反射效率較低的緣由: 經過反射訪問對象的屬性或方法時,須要遍歷該對象全部的屬性或方法,這是反射效率低的主要緣由。 經過反射訪問對象的屬性或方法時,須要進行一些安全檢查,以保證該類的屬性或方法被安全的訪問。 優化: 1>將Field、Method等對象(使用一個Map)緩存起來,訪問時直接從緩存中獲取Field、Method對象。 2>在緩存Field、Method等對象前,忽略它們的訪問權限: eg: // 將Field、Method等對象緩存起來 Field nameField = Person.class.getField("name"); Method helloMethod = Person.class.getMethod("hello"); nameField.setAccessible(true); helloMethod.setAccessible(true); HashMap<String, Field> fieldMap = new HashMap<>(); HashMap<String, Method> methodMap = new HashMap<>(); fieldMap.put("Person_field_name", nameField); methodMap.put("Person_method_hello", helloMethod); // 訪問屬性或方法時直接從緩存中拿到Field、Method等對象 fieldMap.get("Person_field_name").get(obj); // 訪問obj的name屬性的值 methodMap.get("Person_method_hello").invoke(obj); // 調用obj的hello方法