美文网首页
无处不在的反射

无处不在的反射

作者: 布拉德老瓜 | 来源:发表于2021-04-16 21:07 被阅读0次

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

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<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));
  • 获取字段数组

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 <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}

Json工具

SpringIoc创建bean并注入属性

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

相关文章

  • 无处不在的反射

    本文不讲反射的具体实现。 1.反射的原理 - class对象 11)class对象概述 编译阶段,编译器将java...

  • java反射整理

    反射可以说是在android源码中的影子无处不在,为了更好地理解android源码,所以该篇整理出反射中经常看到的...

  • 深入浅出信号反射

    信号频率高到一定程度时,信号的反射几乎无处不在,解决反射问题是硬件工程师一项基本的要求。 哲学上说了事物之间是普遍...

  • 【读书清单】文案创作完全手册之文案写作小窍门

    001 文案无处不在 自从看了这本书后,发现无处不广告,无处不文案。 今天看到赵丽颖的华为广告,条件反射的分析。 ...

  • 月光经过我窗前

    阳光下,足够嘈杂 借月 阳光反射出它的言下之意 月光经过我窗前 没有听见月光 犹如没有听见阳光 安静无处不在 白天...

  • 5120

    上帝也靠近看看人类无处不在的思想。呈现几个局部,局部正在飞驰。 屏幕既呈现也反射。能虚构的眼睛也正被虚构之笔虚构着...

  • .NET进阶篇03-Reflection反射、Attribute

    知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂 一、概述 反射其实无处不在,我们用VS进行调试时候,查看成...

  • Java基础之反射

    Java基础之反射 反射基本介绍 反射的使用通过反射调用属性和方法通过反射获取配置文件 反射基本介绍 Java反射...

  • Java反射与joor反射库的使用

    java原生反射的使用 反射构造对象 反射方法 反射字段 joor反射库的使用 github:https://gi...

  • 艾珍好 冬天咳嗽,要不要吃药?

    天气变冷了,很多人受寒感冒,吃各种药不见好。咳嗽无处不在,有时咳嗽是一种正常的反射动作,有时候是一种病态的反应,长...

网友评论

      本文标题:无处不在的反射

      本文链接:https://www.haomeiwen.com/subject/sowhhltx.html