一履因、概述
1丐谋、Java反射機制(Java-Reflect):
在運行狀態(tài)中,對于任意一個類饲握,都能夠知道這個類中的所有屬性和方法私杜;對于任意一個對象,都能夠調用它的任意一個方法和屬性互拾;這種動態(tài)獲取的信息以及動態(tài)調用對象的方法的功能稱為java的反射機制歪今。
反射是Java開發(fā)中一個非常重要的概念,掌握了反射的知識颜矿,才能更好的學習Java高級課程.
2寄猩、Java 反射機制的功能
在運行時判斷任意一個對象所屬的類。
在運行時構造任意一個類的對象骑疆。
在運行時判斷任意一個類所具有的成員變量和方法田篇。
在運行時調用任意一個對象的方法。
生成動態(tài)代理箍铭。
3泊柬、Java 反射機制的應用場景
逆向代碼 ,例如反編譯
與注解相結合的框架 例如Retrofit
單純的反射機制應用框架 例如EventBus
動態(tài)生成類框架 例如Gson
二诈火、通過Java反射查看類信息
1兽赁、獲得Class對象
每個類被加載之后,系統(tǒng)就會為該類生成一個對應的Class對象冷守。通過該Class對象就可以訪問到JVM中的這個類刀崖。
在Java程序中獲得Class對象通常有如下三種方式:
使用 Class 類的
forName(String clazzName)
靜態(tài)方法。該方法需要傳入字符串參數(shù)拍摇,該字符串參數(shù)的值是某個類的全限定名(必須添加完整包名)亮钦。調用某個類的
class
屬性來獲取該類對應的 Class 對象。調用某個對象的
getClass()
方法充活。該方法是java.lang.Object類中的一個方法蜂莉。
//第一種方式 通過Class類的靜態(tài)方法——forName()來實現(xiàn)
class1 = Class.forName("com.lvr.reflection.Person");
//第二種方式 通過類的class屬性
class1 = Person.class;
//第三種方式 通過對象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();
對于方式一和方式二都是直接根據(jù)類來取得該類的 Class 對象蜡娶,相比之下,方式二有如下的兩種優(yōu)勢:
- 代碼跟安全映穗。程序在編譯階段就能夠檢查需要訪問的 Class 對象是否存在窖张。
- 線程性能更好。因為這種方式無須調用方法男公,所以性能更好荤堪。
可以通過類的類類型創(chuàng)建該類的對象實例。
Class.newInstance();
//
Foot foot = (Foot) c1.newInstance();
2枢赔、從 Class 中獲取信息
一旦獲得了某個類所對應的Class 對象之后澄阳,就可以調用 Class 對象的方法來獲得該對象的和該類的真實信息了。
獲取 Class 對應類的成員變量
Field[] getDeclaredFields();
// 獲取 Class 對象對應類的所有屬性踏拜,與成員變量的訪問權限無關碎赢。
Field[] getFields();
// 獲取 Class 對象對應類的所有 public 屬性。
Field getDeclaredField(String name);
// 獲取 Class 對象對應類的指定名稱的屬性速梗,與成員變量的訪問權限無關肮塞。
Field getField(String name);
// 獲取 Class 對象對應類的指定名稱的 public 屬性。獲取 Class 對應類的方法
Method[] getDeclaredMethods();
// 獲取 Class 對象對應類的所有聲明方法姻锁,于方法的訪問權限無關枕赵。
Method[] getMethods();
// 獲取 Class 對象對應類的所有 public 方法,包括父類的方法位隶。
Method getMethod(String name, Class<?>...parameterTypes);
// 返回此 Class 對象對應類的拷窜、帶指定形參列表的 public 方法。
Method getDeclaredMethod(String name, Class<?>...parameterTypes);
// 返回此 Class 對象對應類的涧黄、帶指定形參列表的方法篮昧,與方法的訪問權限無關。獲取 Class 對應類的構造函數(shù)
Constructor<?>[] getDeclaredConstructors();
// 獲取 Class 對象對應類的所有聲明構造函數(shù)笋妥,于構造函數(shù)的訪問權限無關懊昨。
Constructor<?>[] getConstructors();
// 獲取 Class 對象對應類的所有 public 構造函數(shù)。
Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes);
// 返回此 Class 對象對應類的春宣、帶指定形參列表的構造函數(shù)酵颁,與構造函數(shù)的訪問權限無關。
Constructor<T> getConstructor(Class<?>...parameterTypes);
// 返回此 Class 對象對應類的月帝、帶指定形參列表的 public 構造函數(shù)躏惋。獲取 Class 對應類的 Annotation(注釋)
<A extends Annotation>A getAnnotation(Class<A> annotationClass);
// 嘗試獲取該 Class 對對象對應類存在的、指定類型的 Annotation嫁赏;如果該類型的注解不存在,則返回 null油挥。
<A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass);
// 這是Java8新增的方法潦蝇,該方法嘗試獲取直接修飾該 Class 對象對應類款熬、指定類型的Annotation;如果該類型的注解不存在攘乒,則返回 null贤牛。
Annotation[] getAnnotations();
// 返回修飾該 Class 對象對應類存在的所有Annotation。
Annotation[] getDeclaredAnnotations();
// 返回直接修飾該 Class 對應類的所有Annotation则酝。
<A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass);
// 獲取修飾該類的殉簸、指定類型的多個Annotation。
<A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass);
// 獲取直接修飾該類的沽讹、指定類型的多個Annotation般卑。獲取 Class 對應類的內部類
Class<?>[] getDeclaredClasses();
// 返回該 Class 對象對應類包含的全部內部類。獲取 Class 對應類的外部類
Class<?> getDeclaringClass();
// 返回該 Class 對象對應類所在的外部類爽雄。獲取 Class 對應類所實現(xiàn)的接口
Class<?>[] getInterfaces();
獲取 Class 對應類所繼承的父類
Class<? super T> getSuperClass();
獲取 Class 對應類的修飾符蝠检、所在包、類名等基本信息
int getModifiers();
// 返回此類或接口的所有修飾符挚瘟。修飾符由 public叹谁、protected、private乘盖、final焰檩、static、abstract 等對應的常量組成订框,返回的整數(shù)應使用 Modifier 工具類的方法來解碼析苫,才可以獲取真實的修飾符。
Package getPackage()
// 獲取該類的包布蔗。
String getName()
// 以字符串的形式返回此 Class 對象所表示的類的名稱藤违。
String getSimpleName()
// 以字符串的形式返回此 Class 對象所表示的類的簡稱。判斷該類是否為接口纵揍、枚舉顿乒、注解類型等
boolean isAnnotation()
// 返此 Class 對象是否是一個注解類型(由@interface定義)。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 判斷此 Class 對象是否使用了Annotation修飾泽谨。
boolean isAnonymousClass()
// 此 Class 對象是否是一個匿名類璧榄。
boolean isArray()
//此 Class 對象是否是一個數(shù)組。
boolean isEnum()
// 此 Class 對象是否是一個枚舉(由 enum 關鍵字定義)吧雹。
boolean isInterface()
// 此 Class 對象是否是一個接口(由 interface 關鍵字定義)骨杂。
boolean isInstance(Object obj)
// 判斷 obj 是否是此 Class 對象的實例。該方法完全可以替代 instanceof 操作符雄卷。
3搓蚪、Java 8 新增的方法——參數(shù)反射
Java 8 在java.lang.reflect包下新增了一個 Executable 抽象基類,該對象代表可執(zhí)行的類成員丁鹉,該類派生了 Constructor妒潭、Method 兩個子類悴能。
Executable 基類提供了大量的方法來獲取修飾該方法或構造器的注解信息;還提供了
isVarArgs()
// 用于判斷該方法或構造器是否包含數(shù)量可變的形參雳灾。
getModifiers()
// 獲取該方法或構造器的修飾符漠酿。
此外,Executable 提供了如下兩個方法:
int ParamenterCount()
// 獲取該構造器或方法的形參個數(shù)谎亩。
Paramenter[] getParamenters()
// 獲取該構造器或方法的所有形參炒嘲。
上面的第二個方法返回了一個 Paramenter[] 數(shù)組,Paramenter也是 Java 8 新增的API匈庭,每個 Paramenter 對象代表方法或構造器的一個參數(shù)夫凸。
Paramenter 提供了大量方法來獲取聲明該參數(shù)的泛型信息,還提供了如下常用的方法來獲取參數(shù)信息:
getModifiers()
// 獲取修飾該形參的修飾符嚎花。
String getName()
// 獲取形參名寸痢。
Type getParamenterizedType()
// 獲取帶泛型的形參類型。
Class<?> getType()
// 獲取形參類型紊选。
boolean isNamePresent()
// 返回該類的 class 文件中是否包含了方法的形參名信息啼止。
boolean isVarArgs()
// 用于判斷該參數(shù)是否為個數(shù)可變的形參。
三兵罢、使用反射生成并操作對象
Class 對象可以獲得該類里的方法(由 Method 對象表示)献烦、構造器(由 Constructor 對象表示)、成員變量(由 Field 對象表示)卖词,這三個類都位于 java.lang.reflect 包下巩那。
程序可以通過 Method
對象來執(zhí)行對應的方法,
通過 Constructor
對象來調用對應的構造器創(chuàng)建實例此蜈,
通過 Field
對象直接訪問并修改對象的成員變量值即横。
1、創(chuàng)建對象
通過反射來生成對象的兩種方式:
使用 Class 對象的
newInstance()
方法來創(chuàng)建該 Class 對象對應類的實例裆赵。
要求:Class 對象的對應類要有默認構造器东囚,而執(zhí)行newInstance()
方法實際上是利用默認構造器來創(chuàng)建該類的實例。先使用 Class 對象獲取指定的 Constructor 對象战授,再調用 Constructor 對象的
newInstance()
方法來創(chuàng)建該 Class 對象對應類的實例页藻。
這種方式可以選擇使用指定的構造器來創(chuàng)建實例。
通過第一種方式來創(chuàng)建對象是比較常見的情形植兰,在很多的 JavaEE 框架中都需要根據(jù)配置文件信息來創(chuàng)建Java對象份帐。從配置文件中讀取的只是某個類的字符串類名,程序需要根據(jù)該字符串來創(chuàng)建對應的實例楣导,就必須使用到反射废境。
先建一個配置文件,obj.properties
a=java.util.Date
b=javax.swing.JFrame
/**
* 功能:實現(xiàn)一個簡單的對象處
* 思路:該對象池會根據(jù)配置文件讀取 key-value 對,然后創(chuàng)建這些對象噩凹,
* 并將這些對象放入到一個 HashMap 中朦促。
* @author Administrator
*
*/
public class ObjectPoolFactory {
// 定義一個對象池,<對象名栓始,實際對象>
private Map<String, Object> objectPool = new HashMap<>();
/**
* 創(chuàng)建對象
* @param className 字符串類名
* @return 返回對應的 Java 對象
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
private Object createObject(String className)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
// 獲取對應的 Class 對象
Class<?> clazz = Class.forName(className);
// 使用對應類的默認構造器創(chuàng)建實例
return clazz.newInstance();
}
/**
* 根據(jù)指定文件來初始化對象池
* @param fileName 配置文件名
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public void initPool(String fileName)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
try(FileInputStream fis = new FileInputStream(fileName)) {
Properties props = new Properties();
props.load(fis);
for (String name : props.stringPropertyNames()) {
objectPool.put(name, createObject(props.getProperty(name)));
}
} catch (IOException e) {
System.out.println("讀取" + fileName + "異常!");
}
}
/**
* 根據(jù)指定的 name 值來獲取對應的對象
* @param name
* @return
*/
public Object getObject(String name) {
return objectPool.get(name);
}
}
測試文件:
public class Test {
public static void main(String[] args)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
ObjectPoolFactory opf = new ObjectPoolFactory();
opf.initPool("obj.properties");
System.out.println(opf.getObject("a"));
System.out.println(opf.getObject("b"));
}
}
運行的結果:
這種使用配置文件來配置對象血当,然后由程序根據(jù)配置文件來創(chuàng)建對象的方式非常有用幻赚,如 Spring 框架就采用這種方式大大簡化了 JavaEE 應用的開發(fā),當然臊旭,Spring采用的是 XML 配置文件——因為 XML 配置文件能配置的信息更加的豐富落恼。
第二種方式,利用指定的構造器來創(chuàng)建 Java 對象离熏。
- 獲取該類的 Class 對象佳谦。
- 利用 Class 對象的
getConstrustor()
方法來獲取指定的構造器。 - 調用 Construstor 的
newInstance()
方法來創(chuàng)建 Java 對象滋戳。
public class CreateObject {
public static void main(String[] args) throws Exception {
Class<?> jframeClass = Class.forName("javax.swing.JFrame");
Constructor<?> ctor = jframeClass.getConstructor(String.class);
Object obj = ctor.newInstance("測試窗口");
System.out.println(obj);
}
}
實際上钻蔑,只有當程序需要動態(tài)創(chuàng)建某個類的對象時才會考慮使用反射,通常在開發(fā)通用性比較廣的框架奸鸯、基礎平臺時可能會大量使用反射咪笑。
2、調用方法
當獲得某個類對應的 Class 對象后娄涩,就可以通過該 Class 對象的方法:
getMethods()
// 獲取全部方法窗怒。
getMethod()
// 獲取指定方法。
這兩個方法的返回值是 Method 數(shù)組蓄拣,或者 Method 對象扬虚。
每個 Method 對象對應一個方法,獲得 Method 對象后球恤,程序就可通過該 Method 來調用它對應的方法辜昵。在 Method 里包含一個 invoke()
方法:
Object invoke(Object obj, Object... args)
// 該方法中的 obj 是執(zhí)行該方法的主調,后面的 args 是執(zhí)行該方法時傳入該方法的實參碎捺。
// 生成新的對象:用newInstance()方法
Object obj = class1.newInstance();
// 首先需要獲得與該方法對應的Method對象
Method method = class1.getMethod("setAge", int.class);
// 調用指定的函數(shù)并傳遞參數(shù)
method.invoke(obj, 28);
當通過Method的invoke()
方法來調用對應的方法時路鹰,Java會要求程序必須有調用該方法的權限。如果程序確實需要調用某個對象的 private 方法收厨,則可以先調用以下方法:
setAccessible(boolean flag)
// 值為true晋柱,指示該 Method 在使用時應該取消Java語言的訪問權限檢查;值為false诵叁,則知識該Method在使用時要實施Java語言的訪問權限檢查雁竞。
Method 、 Constructor 、Field 都可以調用該方法碑诉,從而實現(xiàn)通過反射來調用 private 方法彪腔、private 構造器、private 成員變量进栽。
3德挣、訪問成員變量值
通過Class對象的:
getFields()
// 獲取全部成員變量,返回 Field數(shù)組或對象快毛。
getField()
// 獲得指定成員變量格嗅,返回 Field數(shù)組或對象。
Field 提供了兩組方法來讀取或設置成員變量的值:
getXXX(Object obj)
// 獲取obj對象的該成員變量的值唠帝。
setXXX(Object obj, XXX val)
// 將 obj 對象的該成員變量設置成val值屯掖。
//生成新的對象:用newInstance()方法
Object obj = class1.newInstance();
//獲取age成員變量
Field field = class1.getField("age");
//將obj對象的age的值設置為10
field.setInt(obj, 10);
//獲取obj對象的age的值
field.getInt(obj);