java中的反射

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方法
相關文章
相關標籤/搜索