本文不講反射的具體實現(xiàn)麻汰。
1.反射的原理 - class對象
11)class對象概述
編譯階段庐冯,編譯器將java代碼編譯為class文件吐绵。
JVM在類加載階段彼哼,會將class文件中的信息轉(zhuǎn)為方法區(qū)的運行時數(shù)據(jù)对妄,同時在堆中創(chuàng)建一個代表方法區(qū)中對應數(shù)據(jù)入口的Class對象。通過這個Class對象沪羔,我們可以在運行時獲取到類中定義的實例構(gòu)造器饥伊、字段、方法等信息蔫饰。
反射的基本思路就是通過這個Class對象去獲取實例構(gòu)造器,創(chuàng)建實例對象愉豺。然后根據(jù)方法名獲取方法對象篓吁,通過method.invoke()來執(zhí)行方法。
1.2)class對象的獲取
class對象的獲取方式主要有:
- 通過全限定類名獲取
- 通過已有的實例對象來獲取它對應的Class對象
這兩種方式創(chuàng)建過程都是蚪拦,如果要獲取的class已經(jīng)完成了類加載杖剪,Class對象存在于堆中,那么就獲取到這個對象驰贷;如果沒有盛嘿,則類加載創(chuàng)建出對應的Class對象。 它們獲取到的對象都是同一個(單例)括袒。
package reflect;
import java.lang.Class;
public class Solution {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clzHuman = Class.forName("reflect.Human"); //get Class object via Class.forName(name)
Human human = new Human("張三");
Class<? extends Human> aClass = human.getClass(); // get Class object via instance object
System.out.println(aClass == clzHuman); // true
}
}
1.3)字段次兆、方法、構(gòu)造器的獲取
通過Class對象锹锰,可以去訪問到類中定義的屬性芥炭。其中最常用的是構(gòu)造器、字段恃慧、方法园蝠。
1.3.1)構(gòu)造器獲取:
可以通過getConstructor方法來獲取構(gòu)造器痢士,無參的方法即調(diào)用默認無參構(gòu)造器彪薛,如果傳入?yún)?shù)類型的數(shù)組,如下面代碼所示,即可獲取到對應的帶參構(gòu)造器善延。如果在類中沒有對應的構(gòu)造器训唱,則拋出異常。
Class<?> clzMan = Class.forName("reflect.Man");
Constructor<?> constructor = clzMan.getConstructor(new Class[]{String.class});
Man man = (Man)constructor.newInstance("Lonely Man");
System.out.println(man);
// 輸出man:{name=Lonely Man, weight=0.0, friends=null, age=0, height=0}
1.3.2) 字段獲取
-
通過變量名獲取字段
Class.getField(name)返回已加載類聲明的所有public成員變量的Field對象挚冤,包括從父類繼承過來的成員變量况增。
Class.getDeclaredField(name)返回當前類所有成員變量。
返回的Field應該是內(nèi)存中原本Field的拷貝而不是本身训挡,因為獲取到的Field對象是可以修改的澳骤,設(shè)計者當然不希望因為程序員在某處改錯了而給其他使用堆內(nèi)存中該Field的代碼帶來不好的影響。
通過下面的代碼可以獲取到friend字段(Field)澜薄, 通過字段可以獲取到它的訪問權(quán)限修飾符为肮,如果具有訪問權(quán)限,還可以通過字段獲取某個具體實例對象該字段對應的屬性值肤京。
Field friendField = clzMan.getDeclaredField("friends");
friendField.setAccessible(true);
System.out.println(friendField.getName() + ": " +friendField.get(man));
// 驗證返回的Field是拷貝
Field friendField1 = clzMan.getDeclaredField("friends");
System.out.println(friendField == friendField1); // false
System.out.println(friendField1.getName() + ": " +friendField1.get(man)); //拋出IllegalAccessException
以下兩個代碼片段將不會執(zhí)行成功颊艳,原因是friends是Man類的私有成員變量,通過getField只能獲取public權(quán)限的變量忘分。而age不是當前類定義的變量棋枕,通過Class<Man>是不可以獲取的。
//拋出異常
Field friendField = clzMan.getField("friends");
friendField.setAccessible(true);
System.out.println(friendField + ": " + friendField.get(man));
//拋出異常
Field ageField = clzMan.getDeclaredField("age");
ageField.setAccessible(true);
System.out.println(ageField + ": " +ageField.get(man));
如果要獲取age字段妒峦,那么必須先獲取到Class<Human>對象重斑,然后通過這個對象來獲取。
先獲取humanClass肯骇,然后再通過humanClass獲取age字段
Class<?> humanClass = clzMan.getSuperclass();
Field ageField = humanClass.getDeclaredField("age");
ageField.setAccessible(true);
System.out.println(ageField.getName()+": " +ageField.get(man));
-
獲取字段數(shù)組
getFields()方法將返回已加載類聲明的所有public成員變量的Field對象窥浪,包括從父類繼承過來的成員變量。
getDeclaredFields()可以用來返回當前類聲明的所有成員變量
Field[] fields = clzMan.getFields();
for (Field field : fields) {
String fieldNames = "";
fieldNames += field.getName() + ", ";
System.out.println(fieldNames); // 打印""
}
Field[] declaredFields = clzMan.getDeclaredFields();
for (Field field : declaredFields) {
String fieldNames = "";
fieldNames += field.getName() + ", ";
System.out.println(fieldNames); // 打印"friends,"
}
1.3.3)方法對象的獲取
與獲取字段類似
2.三個重要的類:Field, Method,Constructor
2.1)Constructor
Constructor是對構(gòu)造器的抽象笛丙,一個構(gòu)造器對象至少應該包含如下信息:它所屬的類漾脂、參數(shù)類型數(shù)組、異常類型數(shù)組胚鸯、訪問修飾符骨稿。
構(gòu)造器的功能就是初始化一個實例對象。完成該功能的核心方法是newInstance方法蠢琳,該方法會創(chuàng)建一個實例對象啊终,并為對象的屬性賦值。至于如何創(chuàng)建的可以參考對象的創(chuàng)建與訪問定位傲须。
2.2)Field
Field是對字段的抽象蓝牲,它應當包含以下信息:定義它的類、字段名泰讽、字段的類型例衍、訪問修飾符昔期、以及操作實例對象中該字段的屬性的工具(比如為某個具體實例的字段設(shè)置值、獲取字段的值)
Field最核心的功能是get/set某個具體實例該字段的值佛玄。
/**Sets the field represented by this {@code Field} object on the pecified object argument to the specified new value.
*/
@CallerSensitive
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).set(obj, value);
}
/**Returns the value of the field represented by this {@code Field}, onthe specified object
*/
@CallerSensitive
public Object get(Object obj)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
return getFieldAccessor(obj).get(obj);
}
2.3) Method
Method是對方法的抽象硼一,可以是類方法或?qū)嵗椒ǎòǔ橄蠓椒ǎ?br>
方法除了定義它的類、方法名梦抢、訪問修飾符之外般贼,還應當包含方法返回值類型、參數(shù)類型數(shù)組奥吩、異常類型數(shù)組哼蛆,以及執(zhí)行某個具體實例對象中該方法的工具(MethodAccessor).
核心方法是invoke方法
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
常見的使用方式
入門demo
使用反射寫一個將Map轉(zhuǎn)化為Man對象的方法:
package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
// 解析工具類
public class Util <T>{
public Object parseObject(Map<String, ?> map, Class<?> objectClass) throws Exception {
assert objectClass != null;
Constructor<?> constructor = objectClass.getConstructor();
Object o = constructor.newInstance();
while (objectClass != null){
for (String fieldName : map.keySet())
{
Field field = null;
try{
field = objectClass.getDeclaredField(fieldName);
Class<?> type = field.getType();
String typeName = type.getName();
String methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
Method method = objectClass.getDeclaredMethod(methodName, type);
if (type.isPrimitive() || typeName.equals("java.lang.String")){
method.invoke(o, map.get(fieldName));
}
// 使用switch ...case判斷type該如何解析更好,這里是圖demo寫起來省事
// other types like array... are ignored
else {
Map sub = (Map)map.get(fieldName);
Object o1 = parseObject(sub, type);
method.invoke(o, o1);
}
} catch (Exception e){}//e.printStackTrace();
}
Class<?> superclass = objectClass.getSuperclass();
objectClass = superclass;
}
return o;
}
}
//Man
public class Man<T> extends Human {
private Human bestFriend;
public Man() {
}
public Man(String name) {
super(name);
}
public Human getBestFriend() {
return bestFriend;
}
public void setBestFriend(Human friend) {
this.bestFriend = friend;
}
// Solution主方法所在類
public class Solution {
public static void main(String[] args) throws Exception {
Class<?> clzMan = Class.forName("reflect.Man");
Constructor<?> constructor = clzMan.getConstructor(new Class[]{String.class});
Man man = (Man)constructor.newInstance("Lonely Man");
HashMap hashMap = new HashMap();
hashMap.put("age", 10);
hashMap.put("name", "zhangsan");
HashMap friendMap = new HashMap();
friendMap.put("age", 12);
friendMap.put("name", "lisi");
hashMap.put("bestFriend",friendMap);
System.out.println("hashmap: " + hashMap);
Util util = new Util();
Object o = util.parseObject(hashMap, Man.class);
System.out.println("man: " + o);
hashmap: {bestFriend={name=lisi, age=12}, name=zhangsan, age=10}
man: {bestFriend=Human{name='lisi', age=12, height=0, weight=0.0}, name=zhangsan, weight=0.0, age=10, height=0}