深入浅出反射

什么是反射?

反射是一种能够在程序运行时动态访问、修改某个类中任意属性(状态)和方法(行为)的机制(包括private实例和方法),java反射机制提供了以下几个功能:

  • 在运行时判断任意一个对象所属的类;

  • 在运行时构造任意一个类的对象;

  • 在运行时判断任意一个类所具有的成员变量和方法;

  • 在运行时调用任意一个对象的方法。

反射涉及到四个核心类:

  • java.lang.Class.java:类对象;

  • java.lang.reflect.Constructor.java:类的构造器对象;

  • java.lang.reflect.Method.java:类的方法对象;

  • java.lang.reflect.Field.java:类的属性对象;

反射有什么用?

  • 操作因访问权限限制的属性和方法;

  • 实现自定义注解;

  • 动态加载第三方jar包,解决android开发中方法数不能超过65536个的问题;

  • 按需加载类,节省编译和初始化APK的时间;

反射工作原理

当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。

反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。

反射实例

分别演示三种获取类信息的方式、获取当前类的所有方法和获取当前类及其父类的所有方法、获取当前类的所有实例和获取当前类及其父类的所有实例、获取父类信息、获取接口信息、比较反射方法和实例的性能差异等几个方面:

  • 示例类:

父类Personon.java:

package com.eebbk.reflectdemo;

public class Person{
    String mName;
    String mSex;
    public int mAge;

    public Person(String aName, String aSex, int aAge) {
        mName = aName;
        mSex = aSex;
        mAge = aAge;
    }

    public int getmAge(){
        return mAge;
    }

    public void setmAge(int mAge){
        this.mAge = mAge;
    }

    public String getmName(){
        return mName;
    }

    public void setmName(String mName){
        this.mName = mName;
    }

    public String getmSex(){
        return mSex;
    }

    public void setmSex(String mSex){
        this.mSex = mSex;
    }

    private String getDescription(){
        return "黄种人";
    }
}

接口ICompany.java:

package com.eebbk.reflectdemo;

public interface ICompany{
    String getCompany();
}

子类ProgramMonkey.java:

package com.eebbk.reflectdemo;

public class ProgramMonkey extends Person implements ICompany{
    String mLanguage = "C#";
    String mCompany = "BBK";

    public ProgramMonkey(String aName, String aSex, int aAge){
        super(aName, aSex, aAge);
    }

    public ProgramMonkey(String language, String company, String aName, String aSex, int aAge){
        super(aName, aSex, aAge);
        mLanguage = language;
        mCompany = company;
    }

    public String getmLanguage(){
        return mLanguage;
    }

    public void setmLanguage(String mLanguage){
        this.mLanguage = mLanguage;
    }

    private int getSalaryPerMonth(){
        return 12306;
    }

    @Override
    public String getCompany(){
        return mCompany;
    }
}

示例类ReflectActivity.java:

public class ReflectActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reflect_layout);
    }

    public void onClick(View v){
        switch(v.getId()){
            case R.id.getClassObjectBtnId:{
                getClassObject();
            }
            break;
            case R.id.getMethodInfoBtnId:{
                getMethodInfo();
            }
            break;
            case R.id.getFieldInfoBtnId:{
                getFieldInfo();
            }
            break;
            case R.id.getSuperClassInfoBtnId:{
                getSuperClass();
            }
            break;
            case R.id.getInterfaceInfoBtnId:{
                getInterfaces();
            }
            break;
            case R.id.compareMethodAndFieldBtnId:{
                compareCallMethodAndField();
            }
            break;
            default:{

            }
            break;
        }
    }

    private void getClassObject(){
        Class<?> classObject = null;

        classObject = getClassObject_1();
        LogE("classObject_1 name : " + classObject.getName());
        classObject = getClassObject_2();
        LogE("classObject_2 name : " + classObject.getName());
        classObject = getClassObject_3();
        LogE("classObject_3 name : " + classObject.getName());
    }

    private void getMethodInfo(){
        getAllMethods();
        getCurrentClassMethods();
    }

    private void getFieldInfo(){
        getAllFields();
        getCurrentClassFields();
    }

    private void getSuperClass(){
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        Class<?> superClass = programMonkey.getClass().getSuperclass();
        while (superClass != null) {
            LogE("programMonkey's super class is : " + superClass.getName());
            // 再获取父类的上一层父类,直到最后的 Object 类,Object 的父类为 null
            superClass = superClass.getSuperclass();
        }
    }

    private void getInterfaces() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        Class<?>[] interfaceses = programMonkey.getClass().getInterfaces();
        for (Class<?> class1 : interfaceses) {
            LogE("programMonkey's interface is : " + class1.getName());
        }
    }

    private void compareCallMethodAndField(){
        long callMethodCostTime = getCallMethodCostTime(10000);
        LogE("callMethodCostTime == " + callMethodCostTime);
        long callFieldCostTime = getCallFieldCostTime(10000);
        LogE("callFieldCostTime == " + callFieldCostTime);
    }

    private long getCallMethodCostTime(int count){
        long startTime = System.currentTimeMillis();
        for(int index = 0 ; index < count; index++){
            ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
            try{
                Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
                setmLanguageMethod.setAccessible(true);
                setmLanguageMethod.invoke(programMonkey, "Java");
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }catch(InvocationTargetException e){
                e.printStackTrace();
            }catch(NoSuchMethodException e){
                e.printStackTrace();
            }
        }

        return System.currentTimeMillis()-startTime;
    }

    private long getCallFieldCostTime(int count){
        long startTime = System.currentTimeMillis();
        for(int index = 0 ; index < count; index++){
            ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
            try{
                Field ageField = programMonkey.getClass().getDeclaredField("mLanguage");
                ageField.set(programMonkey, "Java");
            }catch(NoSuchFieldException e){
                e.printStackTrace();
            }catch(IllegalAccessException e){
                e.printStackTrace();
            }
        }

        return System.currentTimeMillis()-startTime;
    }

    /**
     * 获取当前类中的方法
     *
     * */
    private void getCurrentClassMethods() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        Method[] methods = programMonkey.getClass().getDeclaredMethods();
        for (Method method : methods) {
            LogE("declared method name : " + method.getName());
        }

        try {
            Method getSalaryPerMonthMethod = programMonkey.getClass().getDeclaredMethod("getSalaryPerMonth");
            getSalaryPerMonthMethod.setAccessible(true);
            // 获取返回类型
            Class<?> returnType = getSalaryPerMonthMethod.getReturnType();
            LogE("getSalaryPerMonth 方法的返回类型 : " + returnType.getName());

            // 获取方法的参数类型列表
            Class<?>[] paramClasses = getSalaryPerMonthMethod.getParameterTypes() ;
            for (Class<?> class1 : paramClasses) {
                LogE("getSalaryPerMonth 方法的参数类型 : " + class1.getName());
            }

            // 是否是 private 函数,属性是否是 private 也可以使用这种方式判断
            LogE(getSalaryPerMonthMethod.getName() + " is private " + Modifier.isPrivate(getSalaryPerMonthMethod.getModifiers()));

            // 执行方法
            Object result = getSalaryPerMonthMethod.invoke(programMonkey);
            LogE("getSalaryPerMonth 方法的返回结果: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取当前类和父类的所有方法
     *
     * */
    private void getAllMethods() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        // 获取当前类和父类的所有方法
        Method[] methods = programMonkey.getClass().getMethods();
        for (Method method : methods) {
            LogE("method name : " + method.getName());
        }

        try {
            Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
            setmLanguageMethod.setAccessible(true);

            // 获取返回类型
            Class<?> returnType = setmLanguageMethod.getReturnType();
            LogE("setmLanguage 方法的返回类型 : " + returnType.getName());

            // 获取方法的参数类型列表
            Class<?>[] paramClasses = setmLanguageMethod.getParameterTypes() ;
            for (Class<?> class1 : paramClasses) {
                LogE("setmLanguage 方法的参数类型 : " + class1.getName());
            }

            // 是否是 private 函数,属性是否是 private 也可以使用这种方式判断
            LogE(setmLanguageMethod.getName() + " is private " + Modifier.isPrivate(setmLanguageMethod.getModifiers()));

            // 执行方法
            Object result = setmLanguageMethod.invoke(programMonkey, "Java");
            LogE("setmLanguage 方法的返回结果: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Class<?> getClassObject_1(){
        return ProgramMonkey.class;
    }

    private Class<?> getClassObject_2(){
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        return programMonkey.getClass();
    }

    private Class<?> getClassObject_3(){
        try{
            return Class.forName("com.eebbk.reflectdemo.ProgramMonkey");
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 得到当前类的所有实例
     *
     * */
    private void getCurrentClassFields() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        // 获取当前类的所有属性
        Field[] publicFields = programMonkey.getClass().getDeclaredFields();
        for (Field field : publicFields) {
            LogE("declared field name : " + field.getName());
        }

        try {
            // 获取当前类的某个属性
            Field ageField = programMonkey.getClass().getDeclaredField("mAge");
            // 获取属性值
            LogE(" my age is : " + ageField.getInt(programMonkey));
            // 设置属性值
            ageField.set(programMonkey, 10);
            LogE(" my age is : " + ageField.getInt(programMonkey));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 得到当前类及其父类的所有实例
     *
     * */
    private void getAllFields() {
        ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
        // 获取当前类和父类的所有属性
        Field[] publicFields = programMonkey.getClass().getFields();
        for (Field field : publicFields) {
            LogE("field name : " + field.getName());
        }

        try {
            // 获取当前类和父类的某个属性
            Field ageField = programMonkey.getClass().getField("mAge");
            LogE(" age is : " + ageField.getInt(programMonkey));
            ageField.set(programMonkey, 8);
            LogE(" my age is : " + ageField.getInt(programMonkey));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void LogE(String msg){
        Log.e("Reflection", "============== " + msg);
    }
}
  • 演示结果:

三种获取类信息的方式:

三种获取类信息的方式

获取当前类的方法、获取当前类和父类的所有方法:

获取当前类的方法、获取当前类和父类的所有方法

获取当前类的所有实例、获取当前类和父类的所有实例:

获取当前类的所有实例、获取当前类和父类的所有实例

获取父类信息:

获取父类信息

获取接口信息:

获取接口信息

比较反射方法和实例的性能差异:

比较反射方法和实例的性能差异

通过上面的示例可以发现,通过反射能够完成之前所描述的事情,并且反射方法比反射实例要慢很多。

反射的特点

优点

  • 灵活、自由度高:不受类的访问权限限制,想对类做啥就做啥;

缺点

  • 性能问题:通过反射访问、修改类的属性和方法时会远慢于直接操作,但性能问题的严重程度取决于在程序中是如何使用反射的。如果使用得很少,不是很频繁,性能将不会是什么问题;

  • 安全性问题:反射可以随意访问和修改类的所有状态和行为,破坏了类的封装性,如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;

  • 兼容性问题:因为反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常;

说明

  • 通过反射访问方法比实例慢很多;

  • 有用到反射的类不能被混淆;

  • 反射存在性能问题,但使用不频繁、按需使用时,对程序性能影响并不大;

  • 反射存在安全性问题,因为可以随意修改类的所有状态和行为(包括private方法和实例);

  • 使用反射访问Android的API时需要注意因为不同API版本导致的兼容性问题;

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,617评论 18 399
  • 今天电脑系统更新完以后突然很卡,不知道原因,eclipse 服务也启动不起来了,就这样别人都在干活,我一个人在这儿...
    魂归潇湘阅读 139评论 0 0
  • 没伞的挨着有伞的人走,靠得再近也躲不过雨;而无伞也有雨过天晴的时候,也能拥有属于自己的阳光天地。
    吕明超阅读 115评论 0 0