反射相关问题与源码解析

反射

可以JVM运行时动态的加载类,还可以调用类的方法或者修改其属性。

反射原理

Class.forName()

  • 时序图
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CtqabHP6-1599973739107)(开发.assets/1599192616691.png)]

  • 源码
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i5DMIxFk-1599973739113)(开发.assets/1599216279463.png)]

clazz.getDeclaredMethod()

  • 时序图
    在这里插入图片描述

  • Class反射的元数据对象:

  • 源码

    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述

method.invoke()

  • 时序图

    在这里插入图片描述

  • 源码:

    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述

  • 比较GenerateMethodAccessorImpl

    在这里插入图片描述

    • 对比
      • 该方法调用15次前,每次都是jvm本地方法调用。
      • 15次后,说明该方法经常使用,专门用asm生成该方法执行类的字节码文件,然后用类加载器加载进来,每次就可以在内存种调用。
补充

反射的优缺点

优点

  • 增加程序的灵活性,避免将程序写死到代码里。

    package cn.yonyong.reflection.testdemo;
     
    interface Fruit { //水果接口
      public void eat() ; //吃水果
    }
     
    class Apple implements Fruit{ //定义苹果
      public void eat() {
        System.out.println("**吃苹果。"); 
      } 
    }
     
    class Orange implements Fruit{
      public void eat() {
        System.out.println("**吃橘子。"); 
      }
    }
     
    /**
     * 反射创造实例的工厂
     */
    class Factory{
      public static Fruit getInstance(String className){
        Fruit fruit = null ;
        try{
          fruit = (Fruit) Class.forName(className).newInstance() ;
        }catch(Exception e ){
          e.printStackTrace() ;
        }
        return fruit ;
      }
    }
     
    public class FactoryDemo{
      public static void main(String args[]){
      //通过工厂类取得接口实例,传入完整的包.类名称
        Fruit f = Factory.getInstance("cn.yonyong.reflection.testdemo.Apple") ;
        if(f!=null){ //判断是否取得接口实例
          f.eat() ;
        }
      }
    }
    
  • 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法

缺点:

  • 性能问题。避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
  • 使用反射会模糊程序内部逻辑。无法在源代码中看到程序的逻辑,带来维护问题。
  • 安全限制。使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了
  • 内部暴露。由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

反射是否真的会让你的程序性能降低?

  • 反射大概比直接调用慢50~100倍,但是需要你在执行100万遍的时候才会有所感觉
  • 判断一个函数的性能,你需要把这个函数执行100万遍甚至1000万遍
  • 如果你只是偶尔调用一下反射,请忘记反射带来的性能影响
  • 如果你需要大量调用反射,请考虑缓存。
  • 你的编程的思想才是限制你程序性能的最主要的因素

单例模式如何实现防反射?

  • 存在问题方法:修改构造器,让它在被要求创建第二个实例的时候抛出异常(即加一个static boolean flag字段)。存在的问题是flag字段仍可以通过反射修改。

  • 正确解决方法:实现单例需编写一个包含单个元素的枚举类型

    public enum SingletonClass
    {
        INSTANCE("instance");
    
        private String name;
    
        SingletonClass(String name){
            this.name = name;
        }
    
        public void test()
        {
            System.out.println("The Test!");
        }
    }
    
  • 枚举原理:编译之后,每个枚举变量会被修适为public static final类型,并且在静态代码块里初始化,所以不可能被修改

    在这里插入图片描述

    在这里插入图片描述

.Class和Class.forName、obj.getClass得到的Class实例是同一个实例还是不同的实例?

相同,但是.Class和Class.forName是编译时决定, 而.getClass()是运行时决定

  • .Class: JVM将使用类的类装载器, 将类装入内存(前提是:类还没有装入内存),不对类做类的初始化工作.返回类的Class的对象(转, 不是很懂)
  • .getClass(): 返回对象运行时的真正对象(有可能存在向上转换)所属的类
  • Class.forName(): 装入类, 并做类的初始化
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值