Java基础:反射
反射注解動態代理相關閱讀
- Java基礎:類加載器
- Java基礎:反射
- Java基礎:注解
- Java基礎:動態代理
1. 反射概述
Java反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
Java 反射機制主要提供了以下功能: 在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。
Java中,反射是一種強大的工具。它使您能夠創建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代表鏈接。反射允許我們在編寫與執行時,使我們的程序代碼能夠接入裝載到JVM中的類的內部信息,而不是源代碼中選定的類協作的代碼。這使反射成為構建靈活的應用的主要工具。但需注意的是:如果使用不當,反射的成本很高。
2. 反射的應用場景
反射是Java中的高級特性,在各種Java框架中都需要使用反射。所以,就算你將來很長一段時間不使用反射,但你使用的框架都大量使用了反射,所以想深入學習框架,那么就一定要學習反射。
框架通常通過反射來識別一個對象的“類型信息”。當你傳遞給框架一個對象時,框架會通過反射來了解對象的真實類型(對象實體的類型,而不是引用的類型),這個類型有幾個構造器,有什么樣的屬性,有什么樣的方法。還可以通過反射調用構造器,調用方法,對屬性進行讀寫操作。
你可能覺得這沒有什么神奇的,那是你還沒了解我說的是什么!你需要再想一想,寫一個方法,參數是Object obj,然后你的方法需要創建一個與參數類型相同的對象出來,還要調用這個對象上的方法。需要注意,參數是Object類型,但用戶調用這個方法時,可能傳遞的不是Object實體對象,它的真實類型有可能是任何類型。
目前好多框架都會用到java的反射機制。比如struts2,sping,hibernate。 如果我們不用struts2,自己寫一個類似的功能也是可以實現的,比如瀏覽器通過HTTP發送數據,而這些數據都是字符串,我們接受到這些字符串時, 可以通過反射去構造一個對象(通過攔截器做成框架的功能),這樣就可以用對象的get和set方法了,而不用原始的getPeremter()方法。事實上, 在struts2出來之前,我們又不想用struts1的ActionForm就做過這樣項目。
Java反射機制主要提供了以下功能:
- 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時判斷任意一個類所具有的成員變量和方法
- 在運行時調用任意一個對象的方法
- 生成動態代理
3. 反射
JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
要想解剖一個類,必須先要獲取到該類的字節碼文件對象。而解剖使用的就是Class類中的方法,所以先要獲取到每一個字節碼文件對應的Class類型的對象。
一句話概括:反射就是把java類中的各種成分映射成相應的java類(Class,Field,Method,Constructor),在程序運行的過程中,動態的訪問java類中的成分,反射還可以實現框架的功能
例如:一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等信息也用一個個的Java類來表示,就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Contructor、Package等。
3.1 反射的主要作用
- 運行時取得類的方法和字段的相關信息。
- 創建某個類的新實例(newInstance())
- 取得字段引用直接獲取和設置對象字段,無論訪問修飾符是什么
- 觀察或操作應用程序的運行時行為
- 調試或測試程序,因為可以直接訪問方法、構造函數和成員字段
- 通過名字調用不知道的方法并使用該信息來創建對象和調用方法
4. 反射從Class類開始
要想使用反射,首先你需要得到Class對象,然后才能通過Class對象獲取Constructor、Field、Method等對象。所有的反射對象都不可能自己來new,說白一點,這些反射對象對應的是class文件上的信息,你怎么可能自己去new呢?如果可以自己去new一個Class類的對象,那么是不是就不用我們再去編寫.java文件,然后再通過編譯器去編譯成.class文件了呢?當然這是不可能的!
我們需要思考,Class除了可以返回當前對應類型的所有屬性、方法、構造器的反射對象外,還有什么功能呢?例如對應類型的類名是什么?對應類型的父類是誰?對應類型是不是public類,是不是final類。對應類型有沒有可能是個數組類型?有沒有可能是接口類型?有沒有可能是基本類型等等!如果你學會了這樣思考,那么你今后學習新類是就方便多了!
4.1 三種獲取Class對象的方式
- Object類的getClass()方法
- 數據類型的靜態屬性class
任意數據類型都具備一個class靜態屬性,看上去要比第一種方式簡單
- 將類名作為字符串傳遞給Class類中的靜態方法forName即可
4.2 第三種和前兩種的區別
前兩種你必須明確Person類型;后面是你我這種類型的字符串就行.這種擴展更強.我不需要知道你的類.我只提供字符串,按照配置文件加載就可以了。
PS:所謂的框架就是對外提供一些接口,也就是功能擴展的標準,由實現類按照這個接口標準去實現。框架內部如果需要操縱這些實現類的對象完成某些操作,那么只需要把這些實現類的全名(包名+類名)寫在某個配置文件中,框架代碼只需要讀取這個配置文件,就可以獲取這個實現類的字節碼文件,然后利用反射技術創建這個實現類的對象并且調用相應的方法完成一些操作。
用于描述字節碼的類就是Class類,創建對象,可以提取字節碼文件中的內容,如字段、構造函數、一般函數。該類就可以獲取字節碼文件中的所有內容,那么反射就是依靠該類完成的。想要對一個類文件進行解剖,只要獲取到該類的字節碼文件對象即可。
5. 加載類
我們已經知道,main()方法是程序的入口。那是不是在main()方法開始執行之前,所有的class文件都已經加載到方法區中了呢?答案是:NO!通常只有需要執行到使用某個類的代碼時,才會去CLASSPATH中加載class文件,如果程序從頭到尾都沒有使用某個類,那么這個類對應的class文件就不會被加載到內存。
可以導致一個類被加載可能有:
- 使用一個類的靜態方法;
- 使用一個類的靜態屬性;
- 創建這個類的對象;
- 使用Class.forName()方法加載類;
- 反序列化一個類的對象;
- 加載一個類的子類時,也會加載其父類;
- 加載一個類時,也會加載與該類相關的類。
上面給出的幾個可能也只是可能而已,如果當前類沒有被加載過,才會去加載,如果已經加載到方法區中了,那么就不可能再去加載。
6. Class 字節碼
Class 類的實例表示正在運行的 Java 應用程序中的類和接口。
| forName() | 通過類名獲取類的字節碼 |
| getClassLoader() | 獲取該類的類加載器 |
| getInterfaces() | 獲取所實現的接口 |
| getSuperclass() | 獲取父類 |
| getGenericSuperclass() | 獲取傳遞給父類參數化類型 |
| newInstance() | 創建實例 |
| getName() | 獲取類名,接口名 |
| getPackage() | 獲取包名 |
| isPrimitive() | 判定指定的 Class 對象是否表示一個基本類型 |
| isArray() | 判定此 Class 對象是否表示一個數組類 |
| getResourceAsStream() | 查找具有給定名稱的資源 |
6.1 獲取注解
| getAnnotation() | 獲取指定類型的注解 |
| getAnnotations() | 獲取所有的注解 |
| getDeclaredAnnotations() | 獲取除了繼承得到的所有注解 |
6.2 獲取構造方法
| getConstructor() | 獲取指定的非私有的構造方法 |
| getDeclaredConstructor() | 獲取指定的構造方法 |
| getConstructors() | 獲取公有的構造方法 |
| getDeclaredConstructors() | 獲取所有的構造方法 |
6.3 獲取成員方法
| getMethod() | 獲取指定的非私有方法 |
| getDeclaredMethod() | 獲取指定的方法 |
| getMethods() | 獲取公有的方法 |
| getDeclaredMethods() | 獲取所有的方法 |
6.4 獲取成員變量
| getField() | 獲取指定名稱的字段 |
| getFields() | 獲取公有的字段 |
| getDeclaredField(String name) | 獲取指定名稱的字段 |
| getDeclaredFields() | 獲取所有的字段 |
7. AccessibleObject
AccessibleObject 類是 Field、Method 和 Constructor 對象的基類
| getAnnotation() | 獲取指定類型的注解 |
| getAnnotations() | 獲取所有的注解 |
| getDeclaredAnnotations() | 獲取除了繼承得到的所有注解 |
| setAccessible(true) | 暴力反射,取消訪問檢查 |
8. Constructor
Constructor 提供關于類的單個構造方法的信息以及對它的訪問權限
| newInstance() | 通過構造方法創建實例 |
| getParameterTypes() | 獲取構造器的所有參數的類型 |
| getExceptionTypes() | 獲取構造器上聲明的所有異常類型 |
| getDeclaringClass() | 獲取構造器所屬的類型 |
| getModifiers() | 獲取構造器上的所有修飾符信息 |
9. Method
表示一個類中的成員方法
| invoke(Object obj, Object… args) | 通過方法反射對象調用方法,如果當前方法是實例方法,那么當前對象就是obj,如果當前方法是static方法,那么可以給obj傳遞null。args表示是方法的參數 |
| setAccessible(true) | 暴力反射,取消訪問檢查 |
| getAnnotation() | 獲取方法上指定類型的注解 |
| getAnnotations() | 獲取所有的注解 |
| getDeclaredAnnotations() | 獲取方法上說所有的注解 |
| getGenericParameterTypes() | 獲取泛型的參數化類型 |
10. Field
表示一個類中的成員變量
| getAnnotation() | 獲取字段上指定類型的注解 |
| getAnnotations() | 獲取所有的注解 |
| getDeclaredAnnotations() | 獲取字段所有的注解 |
| set() | 給指定字段設置新值 |
| get() | 獲取字段值 |
| setAccessible(true) | 暴力反射,取消訪問檢查 |
| getType() | 獲取字段的類型 |
| getXXX(Object obj) | 如果當前屬性為基本類型,可以使用getXXX()系列方法獲取基本類型屬性值 |
| setXXX(Object obj, XXX value) | 如果當前屬性為基本類型,可以使用setXXX()系統方法基本類型屬性值 |
11. Type
Type 是 Java 編程語言中所有類型的公共高級接口
11.1 ParameterizedType
ParameterizedType 表示參數化類型,如 Collection
| Type[ ] getActualTypeArguments() | 獲取真實參數 |
12. Array
Array 類提供了動態創建和訪問 Java 數組的方法
| Array.getLenght() | 獲取數組的長度 |
| Array.get() | 獲取數組中指定索引的值 |
13. Modifier
Modifier類有一系列的static方法用來解析其他getModifiers()方法返回的int值
Method m = … int m = m.getModifiers(); boolean b1 = Modifier.isAbstract(m);//解析m中是否包含abstract修飾 boolean b2 = Modifier.isStatic(m);//解析m中是否包含static修飾 String s = Modifiers.toString(m);//把所有修飾都轉換成字符串14. 反射的應用
通過反射獲取構造方法并使用
package cn.itcast_02;import java.lang.reflect.Constructor;/** 需求:通過反射獲取私有構造方法并使用* private Person(String name){}* * Person p = new Person("風清揚");* System.out.println(p);*/ public class ReflectDemo3 {public static void main(String[] args) throws Exception {// 獲取字節碼文件對象Class c = Class.forName("cn.itcast_01.Person");// 獲取私有構造方法對象// NoSuchMethodException:每個這個方法異常// 原因是一開始我們使用的方法只能獲取公共的,下面這種方式就可以了。Constructor con = c.getDeclaredConstructor(String.class);// 用該私有構造方法創建對象// IllegalAccessException:非法的訪問異常。// 暴力訪問con.setAccessible(true);// 值為true則指示反射的對象在使用時應該取消Java語言訪問檢查。Object obj = con.newInstance("風清揚");System.out.println(obj);} }通過反射獲取成員變量并使用
package cn.itcast_03;import java.lang.reflect.Constructor; import java.lang.reflect.Field;/** 通過發生獲取成員變量并使用*/ public class ReflectDemo {public static void main(String[] args) throws Exception {// 獲取字節碼文件對象Class c = Class.forName("cn.itcast_01.Person");// 獲取所有的成員變量// Field[] fields = c.getFields();// Field[] fields = c.getDeclaredFields();// for (Field field : fields) {// System.out.println(field);// }/** Person p = new Person(); p.address = "北京"; System.out.println(p);*/// 通過無參構造方法創建對象Constructor con = c.getConstructor();Object obj = con.newInstance();System.out.println(obj);// 獲取單個的成員變量// 獲取address并對其賦值Field addressField = c.getField("address");// public void set(Object obj,Object value)// 將指定對象變量上此 Field 對象表示的字段設置為指定的新值。addressField.set(obj, "北京"); // 給obj對象的addressField字段設置值為"北京"System.out.println(obj);// 獲取name并對其賦值// NoSuchFieldExceptionField nameField = c.getDeclaredField("name");// IllegalAccessExceptionnameField.setAccessible(true);nameField.set(obj, "林青霞");System.out.println(obj);// 獲取age并對其賦值Field ageField = c.getDeclaredField("age");ageField.setAccessible(true);ageField.set(obj, 27);System.out.println(obj);} }通過反射獲取成員方法并使用
package cn.itcast_04;import java.lang.reflect.Constructor; import java.lang.reflect.Method;public class ReflectDemo {public static void main(String[] args) throws Exception {// 獲取字節碼文件對象Class c = Class.forName("cn.itcast_01.Person");// 獲取所有的方法// Method[] methods = c.getMethods(); // 獲取自己的包括父親的公共方法// Method[] methods = c.getDeclaredMethods(); // 獲取自己的所有的方法// for (Method method : methods) {// System.out.println(method);// }Constructor con = c.getConstructor();Object obj = con.newInstance();/** Person p = new Person(); p.show();*/// 獲取單個方法并使用// public void show()// public Method getMethod(String name,Class<?>... parameterTypes)// 第一個參數表示的方法名,第二個參數表示的是方法的參數的class類型Method m1 = c.getMethod("show");// obj.m1(); // 錯誤// public Object invoke(Object obj,Object... args)// 返回值是Object接收,第一個參數表示對象是誰,第二參數表示調用該方法的實際參數m1.invoke(obj); // 調用obj對象的m1方法System.out.println("----------");// public void method(String s)Method m2 = c.getMethod("method", String.class);m2.invoke(obj, "hello");System.out.println("----------");// public String getString(String s, int i)Method m3 = c.getMethod("getString", String.class, int.class);Object objString = m3.invoke(obj, "hello", 100);System.out.println(objString);// String s = (String)m3.invoke(obj, "hello",100);// System.out.println(s);System.out.println("----------");// private void function()Method m4 = c.getDeclaredMethod("function");m4.setAccessible(true);m4.invoke(obj);} }14.1 反射應用舉例
給ArrayList<Integer>的一個對象,在這個集合中添加一個字符串數據,如何實現呢?
package cn.itcast.test;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList;/** 我給你ArrayList<Integer>的一個對象,我想在這個集合中添加一個字符串數據,如何實現呢?*/ public class ArrayListDemo {public static void main(String[] args) throws NoSuchMethodException,SecurityException, IllegalAccessException,IllegalArgumentException, InvocationTargetException {// 創建集合對象ArrayList<Integer> array = new ArrayList<Integer>();// array.add("hello");// array.add(10);Class c = array.getClass(); // 集合ArrayList的class文件對象Method m = c.getMethod("add", Object.class);m.invoke(array, "hello"); // 調用array的add方法,傳入的值是hellom.invoke(array, "world");m.invoke(array, "java");System.out.println(array);} }通過配置文件運行類中的方法
package cn.itcast.test;import java.io.FileReader; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Properties;/** 通過配置文件運行類中的方法* * 反射:* 需要有配置文件配合使用。* 用class.txt代替。* 并且你知道有兩個鍵。* className* methodName*/ public class Test {public static void main(String[] args) throws Exception {// 反射前的做法// Student s = new Student();// s.love();// Teacher t = new Teacher();// t.love();// Worker w = new Worker();// w.love();// 反射后的做法// 加載鍵值對數據Properties prop = new Properties();FileReader fr = new FileReader("class.txt");prop.load(fr);fr.close();// 獲取數據String className = prop.getProperty("className");String methodName = prop.getProperty("methodName");// 反射Class c = Class.forName(className);Constructor con = c.getConstructor();Object obj = con.newInstance();// 調用方法Method m = c.getMethod(methodName);m.invoke(obj);} }寫一個方法:public void setProperty(Object obj, String propertyName, Object value){},此方法可將obj對象中名為propertyName的屬性的值設置為value
package cn.itcast.test;import java.lang.reflect.Field;public class Tool {public void setProperty(Object obj, String propertyName, Object value)throws NoSuchFieldException, SecurityException,IllegalArgumentException, IllegalAccessException {// 根據對象獲取字節碼文件對象Class c = obj.getClass();// 獲取該對象的propertyName成員變量Field field = c.getDeclaredField(propertyName);// 取消訪問檢查field.setAccessible(true);// 給對象的成員變量賦值為指定的值field.set(obj, value);} } package cn.itcast.test;public class ToolDemo {public static void main(String[] args) throws NoSuchFieldException,SecurityException, IllegalArgumentException, IllegalAccessException {Person p = new Person();Tool t = new Tool();t.setProperty(p, "name", "林青霞");t.setProperty(p, "age", 27);System.out.println(p);System.out.println("-----------");Dog d = new Dog();t.setProperty(d, "sex", '男');t.setProperty(d, "price", 12.34f);System.out.println(d);} }class Dog {char sex;float price;@Overridepublic String toString() {return sex + "---" + price;} }class Person {private String name;public int age;@Overridepublic String toString() {return name + "---" + age;} }總結
- 上一篇: RecyclerView列表控件漂亮时间
- 下一篇: Java基础:基本数据类型包装类