基本概念
java的反射機(jī)制是動態(tài)獲取類的信息以及動態(tài)調(diào)用對象的方法翩腐。這種機(jī)制允許程序在運(yùn)行時通過reflection apis獲取一個已知名稱的class的內(nèi)部信息。包括:modifiers(如public余蟹,static等),superclass(如Object),實現(xiàn)了的Interfaces(如Serializable),也包括其fields和methods的所有信息验辞,并可以在運(yùn)行時動態(tài)改變fields或調(diào)用methods。
- 主要功能
- 在運(yùn)行時判斷任意一個對象所屬的類昂拂。
- 在運(yùn)行時構(gòu)造任意一個類的對象受神。
- 在運(yùn)行時判斷任意一個類所具有的成員變量和方法。
- 在運(yùn)行時調(diào)用任意一個對象的方法格侯。
Java Reflection API簡介
在JDK中鼻听,主要由以下類來實現(xiàn)Java反射機(jī)制,這些類(除了第一個)都位于java.lang.reflect包中联四。
- Class類:代表一個類撑碴,位于java.lang包下。
- Field類:代表類的成員變量(及類的屬性)朝墩。
- Method:代表類的方法醉拓。
- Constructor類:代表類的構(gòu)造方法。
- Array類:提供了動態(tài)創(chuàng)建數(shù)組收苏,以及訪問數(shù)據(jù)的元素的靜態(tài)方法亿卤。
Class對象
要想使用放射,首先需要獲得帶操作的類所對應(yīng)的Class對象鹿霸。java中排吴,無論生成某個類的多少對象,這些對象都會對應(yīng)于同一個Class對象懦鼠。這個Class對象是有JVM生成的钻哩,通過它能夠獲取整個類的結(jié)構(gòu)。
獲取Class對象的3中方法
- 使用Class類的靜態(tài)方法
Class<?> stringClass = Class.forName("java.lang.String");
- 使用類的.class方法
Class<?> stringClass2 = String.class;
- 使用對象的getClass方法
String str = "aa";
Class<?> stringClass3 = str.getClass();
getClass方法定義在Object類中肛冶,不是靜態(tài)方法街氢,聲明為final,不能被子類覆寫睦袖。
使用方法
- 打印類所有方法信息
public static void main(String[] args) throws ClassNotFoundException {
// 獲取class對象
Class<?> classType = Class.forName("java.lang.String");
// 返回class對象所對應(yīng)類或接口中聲明德爾所有方法的數(shù)組【包括私有方法】
Method[] methods = classType.getDeclaredMethods();
// 輸出所有方法的聲明
for (Method method : methods) {
System.out.println(method);
}
}
通過反射調(diào)用方法
public class Reflect {
public int add(int param1, int param2) {
return param1 + param2;
}
public String echo(String message) {
return "Hello " + message;
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 正常的執(zhí)行手段
Reflect reflect = new Reflect();
System.out.println(reflect.add(1, 2));
System.out.println(reflect.echo("suys"));
System.out.println("================================");
// 反射的方法
// 獲取class對象
Class<?> classType = Reflect.class;
// 生成新的對象
Reflect reflect1 = (Reflect) classType.newInstance();
System.out.println(reflect1 instanceof Reflect); // 判斷是否是Reflect類的對象
// 通過反射調(diào)用方法
// 首先需要獲得與該方法對應(yīng)的Method對象珊肃,第一個參數(shù)是方法名,第二個參數(shù)是所需要的參數(shù)Class對象的數(shù)組
Method addMethod = classType.getMethod("add", new Class[]{int.class, int.class});
// 調(diào)用目標(biāo)方法
Object result = addMethod.invoke(reflect1, new Object[]{1, 2});
System.out.println(result); //此時返回的是Integer類型
// 第二個方法調(diào)用采用類似的方法
}
}
兩種用法
// 接口
public interface CarInterface {
public String getName();
public String getSpeed();
}
// 實現(xiàn)類1
public class SmallCar implements CarInterface {
@Override
public String getName() {
return "small car";
}
@Override
public String getSpeed() {
return "100-200";
}
}
// 實現(xiàn)類2
public class BigCar implements CarInterface{
@Override
public String getName() {
return "big car";
}
@Override
public String getSpeed() {
return "10-50";
}
}
// 測試類
public class Test {
public static void main(String[] args) throws Exception {
List<Class<?>> carInterfaces = Lists.newArrayList();
carInterfaces.add(SmallCar.class);
carInterfaces.add(BigCar.class);
for(Class<?> carInterface : carInterfaces) {
if (carInterface instanceof Class) {
Object obj = carInterface.newInstance();
System.out.println(carInterface.getName());
Method method= carInterface.getMethod("getName");
Object result = method.invoke(obj);
System.out.println(result);
}
}
}
public static void main2(String[] args) throws Exception {
List<CarInterface> carInterfaces = Lists.newArrayList();
carInterfaces.add(new BigCar());
carInterfaces.add(new SmallCar());
for(CarInterface carInterface : carInterfaces) {
if (carInterface instanceof CarInterface) {
Class<?> cc = carInterface.getClass();
System.out.println(cc.getName());
Method method= cc.getMethod("getName");
Object result = method.invoke(carInterface);
System.out.println(result);
}
}
}
}
生成對象
如果像拖過類的不帶參數(shù)的構(gòu)造方法來生成對象馅笙,有兩種方式:
- 先獲得Class對象伦乔,然后通過該Class對象的newInstance()方法直接生成
Class<?> classType = String.class;
Object obj = classType.newInstance;
- 先獲得Class對象,然后通過改對象獲得對應(yīng)的Constructor對象延蟹,在通過Constructor對象的newInstance()方法生成(其中Customer是一個自定義的類评矩,有一個無參數(shù)的構(gòu)造函數(shù),也有帶參數(shù)的構(gòu)造方法)
Class<?> classType = Customer.class;
Constructor con = classType.getConstructor(new Class[]{});
Object obj = con.newInstance(new Object[]{});
Object result = classType.getMethod("getName").invoke(obj);
如果想通過類的帶參數(shù)的構(gòu)造方法生成對象阱飘,只能使用上面的第二種方法斥杜。
可以看出調(diào)用構(gòu)造方法生成對象的方法和調(diào)用一般方法的類似,不同的是從Constructor對象時不需要指定名字沥匈,而獲取Method對象時需要指定名字蔗喂。
利用反射實現(xiàn)對象拷貝的例子
// customer類
public class Customer {
private String name;
private String email;
public Customer() {
}
public Customer(String a, String b) {
name = a;
email = b;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 類copy以及測試方法
public Object copy(Object object) throws Exception {
Class<?> classType = object.getClass();
Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
// 獲得對象的所有成員變量
Field[] fields = classType.getDeclaredFields();
for (Field field : fields) {
// 獲取成員變量的名字
String name = field.getName();
// 獲取get和set方法
String firstLetter = name.substring(0, 1).toUpperCase(); // 將屬性的首字母轉(zhuǎn)換為大寫
String getMethodName = "get" + firstLetter + name.substring(1);
String setMethodName = "set" + firstLetter + name.substring(1);
// 獲取方法對象
Method getMethod = classType.getMethod(getMethodName, new Class[]{});
Method setMethod = classType.getMethod(setMethodName, new Class[]{field.getType()}); //注意set方法需要傳入?yún)?shù)類型
// 調(diào)用get方法獲取舊的對象的值
Object value = getMethod.invoke(object, new Object[]{});
// 調(diào)用set方法將這個值賦值到新的對象中去
setMethod.invoke(objectCopy, new Object[]{value});
}
return objectCopy;
}
public static void main(String[] args) throws Exception {
Customer customer = new Customer("Tom", "tom@163.com");
Reflect reflect = new Reflect();
Customer customer1 = (Customer)reflect.copy(customer);
System.out.println(customer1.getEmail() + "," + customer1.getName());
}
反射與數(shù)組
java.lang.Array類提供了動態(tài)創(chuàng)建和訪問數(shù)組元素的各種靜態(tài)方法。
下面例子創(chuàng)建了一個長度為10的字符串?dāng)?shù)組高帖,接著把索引位置為5的元素設(shè)為Hello缰儿,然后在讀取索引位置為5的元素的值。
public static void main(String[] args) throws Exception {
Class<?> classType = Class.forName("java.lang.String");
// 生成數(shù)組散址,指定元素類型和數(shù)組長度
Object array = Array.newInstance(classType, 10);
Array.set(array, 5, "hello");
System.out.println((String)Array.get(array, 5));
}
多維數(shù)組
首先區(qū)別一下下面兩者
System.out.println(Integer.TYPE); ==> int
System.out.println(Integer.class); ==>java.lang.Integer
public static void main(String[] args) {
int[] dims = new int[]{5, 10, 15};
// 注意區(qū)分以下兩種
System.out.println(Integer.TYPE); // int
System.out.println(Integer.class); // Integer
// 創(chuàng)建一個三維數(shù)組乖阵,這個數(shù)組的三個維度分別是5,10,15
Object array = Array.newInstance(Integer.TYPE, dims);
// 可變參數(shù)宣赔,也可以這樣寫
//Object array = Array.newInstance(Integer.TYPE, 5, 10, 15);
System.out.println(array instanceof int[][][]);
Class<?> classType = array.getClass().getComponentType(); //返回數(shù)組元素類型
System.out.println(classType); // 三維數(shù)組的元素為二維數(shù)組,輸出:CLass[[
// 獲得第一層的索引為3的數(shù)組瞪浸,返回的是一個二維數(shù)組
Object arrayObject = Array.get(array, 3);
Class<?> classType1 = arrayObject.getClass().getComponentType(); // 返回數(shù)組的元素類型
System.out.println(classType1); // 二維數(shù)組的元素為一維數(shù)組儒将,輸出:class [
// 此處返回的是一個一維數(shù)組
arrayObject = Array.get(arrayObject, 5);
Class<?> classType2 = arrayObject.getClass().getComponentType(); //返回數(shù)組元素類型
System.out.println(classType2); // 一維數(shù)組的元素為int
// 給一維數(shù)組下標(biāo)為10的位置設(shè)置值為37
Array.setInt(arrayObject, 10, 37);
int[][][] arrayCast = (int[][][]) array;
System.out.println(arrayCast[3][5][10]);
}
利用反射調(diào)用私有方法、訪問私有屬性
利用反射对蒲,首先是Class對象的獲取钩蚊,之后是Method和Field的udixiang的獲取。
以Method為例蹈矮,可以看到getMethod()返回的是public的Method對象砰逻,而getDeclaredMethod()返回Method對象可以是非public的。
Field的方法同理泛鸟。
訪問私有屬性和方法蝠咆,在使用前要通過AccessibleObject類(Constructor、Field和Method類的基類)中的setAccesible()方法來抑制Java訪問權(quán)限的檢查谈况。
實例1.調(diào)用私有方法
// 有私有方法的類
public class PrivateClass {
private String sayHello(String name) {
return "Hello: " + name;
}
}
// 利用反射機(jī)制訪問該方法
public class TestPrivate {
public static void main(String[] args) throws Exception {
PrivateClass privateClass = new PrivateClass();
Class<?> classType = privateClass.getClass();
// 獲取Method對象
Method method = classType.getDeclaredMethod("sayHello", new Class[]{String.class});
// 抑制Java的訪問控制檢查
method.setAccessible(true);
// 如果不加上面語句,報錯
// Class com.suys.java.reflect.TestPrivate can not access a member of class com.suys.java.reflect.PrivateClass with modifiers "private"
String str = (String)method.invoke(privateClass, new Object[]{"suys"});
System.out.println(str);
}
}
實例2.訪問私有屬性
// 有私有屬性的類
public class PrivateClass2 {
private String name = "zhangsan";
public String getName() {
return name;
}
}
//測試方法
public static void main(String[] args) throws Exception {
PrivateClass2 privateClass2 = new PrivateClass2();
Class<?> classType = privateClass2.getClass();
Field field = classType.getDeclaredField("name");
field.setAccessible(true);
field.set(privateClass2, "lisi");
System.out.println(privateClass2.getName());
}
Class類
java中的Object是所有類的繼承根源勺美,其中g(shù)etClass()方法返回一個Class Object。所有的類都有這個方法碑韵。
Class類十分特殊赡茸,它和其他類一樣繼承自O(shè)bject,其實體用以表達(dá)java程序運(yùn)行時的classes和interfaces祝闻,也用來表達(dá)enum占卧、array、primitive Java types(boolean,byte,char,short,int,long,float,double)以及關(guān)鍵詞void联喘。
當(dāng)一個class被加載华蜒,或當(dāng)加載器(class loader)的defineClass()被JVM調(diào)用,JVM便自動產(chǎn)生一個Class對象(Class object)豁遭。
如果想借由修改Java標(biāo)準(zhǔn)庫源碼來觀察Class對象的實際生成時機(jī)叭喜,例如在Class的constructor內(nèi)添加println,那是不可以的蓖谢,因為Class沒有public constructor捂蕴。
Class是Reflection的起源,針對任何類闪幽,唯有先為它產(chǎn)生一個Class object啥辨,接下來才能經(jīng)由后者喚起為數(shù)十多個的Reflection APIs。