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