无处不在的反射

时间:2021-6-7 作者:qvyue

本文不讲反射的具体实现。

1.反射的原理 – class对象

11)class对象概述

编译阶段,编译器将java代码编译为class文件。

无处不在的反射
class文件结构大致长这样

JVM在类加载阶段,会将class文件中的信息转为方法区的运行时数据,同时在堆中创建一个代表方法区中对应数据入口的Class对象。通过这个Class对象,我们可以在运行时获取到类中定义的实例构造器、字段、方法等信息。
反射的基本思路就是通过这个Class对象去获取实例构造器,创建实例对象。然后根据方法名获取方法对象,通过method.invoke()来执行方法。

1.2)class对象的获取

class对象的获取方式主要有:

  1. 通过全限定类名获取
  2. 通过已有的实例对象来获取它对应的Class对象
    这两种方式创建过程都是,如果要获取的class已经完成了类加载,Class对象存在于堆中,那么就获取到这个对象;如果没有,则类加载创建出对应的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)字段、方法、构造器的获取

通过Class对象,可以去访问到类中定义的属性。其中最常用的是构造器、字段、方法。

1.3.1)构造器获取:

可以通过getConstructor方法来获取构造器,无参的方法即调用默认无参构造器,如果传入参数类型的数组,如下面代码所示,即可获取到对应的带参构造器。如果在类中没有对应的构造器,则抛出异常。

        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应该是内存中原本Field的拷贝而不是本身,因为获取到的Field对象是可以修改的,设计者当然不希望因为程序员在某处改错了而给其他使用堆内存中该Field的代码带来不好的影响。
通过下面的代码可以获取到friend字段(Field), 通过字段可以获取到它的访问权限修饰符,如果具有访问权限,还可以通过字段获取某个具体实例对象该字段对应的属性值。

        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

以下两个代码片段将不会执行成功,原因是friends是Man类的私有成员变量,通过getField只能获取public权限的变量。而age不是当前类定义的变量,通过Class是不可以获取的。

        //抛出异常
        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对象,然后通过这个对象来获取。

        先获取humanClass,然后再通过humanClass获取age字段
        Class> humanClass = clzMan.getSuperclass();
        Field ageField = humanClass.getDeclaredField("age");
        ageField.setAccessible(true);
        System.out.println(ageField.getName()+": " +ageField.get(man));
  • 获取字段数组

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是对构造器的抽象,一个构造器对象至少应该包含如下信息:它所属的类、参数类型数组、异常类型数组、访问修饰符。
构造器的功能就是初始化一个实例对象。完成该功能的核心方法是newInstance方法,该方法会创建一个实例对象,并为对象的属性赋值。至于如何创建的可以参考对象的创建与访问定位。

2.2)Field

Field是对字段的抽象,它应当包含以下信息:定义它的类、字段名、字段的类型、访问修饰符、以及操作实例对象中该字段的属性的工具(比如为某个具体实例的字段设置值、获取字段的值)
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是对方法的抽象,可以是类方法或实例方法(包括抽象方法)
方法除了定义它的类、方法名、访问修饰符之外,还应当包含方法返回值类型、参数类型数组、异常类型数组,以及执行某个具体实例对象中该方法的工具(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);
    }
无处不在的反射
method.invoke流程

常见的使用方式

入门demo

使用反射写一个将Map转化为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 {
     public Object parseObject(Map 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 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}

Json工具

SpringIoc创建bean并注入属性

Mybatis创建方法返回对象并注入属性

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。