Java-Day-33 ( 引出反射 + 反射机制 + 反射的优缺点 )

发布时间 2023-07-20 15:58:10作者: 朱呀朱~

Java-Day-33

引出反射

( reflection )

  • 引出

    • 传统 new 方法调用其方法:

      Dog dog = new Dog();
      dog.hello();
      
    • 但若要根据以下配置文件指定信息,创建 Dog 对象并调用方法 hello:

      classfullpath=com.zyz.Dog
      method=hello
      
    • 使用 Properties 类,可以读写配置文件

      Properties properties = new Properties();
      properties.load(new FileInputStream("src\\main\\resources\\zyz.properties"));
      String classfullpath = properties.get("classfullpath").toString();
      String methodName = properties.get("method").toString();
      System.out.println("classfullpath=" + classfullpath); // com.zyz.Dog
      System.out.println("methodName=" + methodName); // hello
      
    • 传统的 new 方法行不通

    • 但这样的需求在学习框架时有很多,即通过外部文件配置,在不修改源码的情况下来控制程序,也符合设计模式的 ocp 原则

      • ocp 原则即开闭原则:不修改源码来扩展功能
  • 使用反射机制解决

    • 加载类,返回 Class 类型的对象 cls

      Class cls = Class.forName(classfullpath); // 上面读写到的classfullpath
      
    • 通过 cls 得到加载的类 com.java.Dog 的对象实例

      Object o = cls.newInstance();
      System.out.println(o.getClass()); // (运行类型)class com.zyz.Dog
      
    • 这样的话想调用方法就要强转成 Dog,才能 o.hello(); 调用,但目前需要的是在不知道其有叫 hello 方法的情况下调用 zyz.properties 配置文件的 method 方法:

    • 通过 cls 得到你加载的类的方法名为 hello 的方法代表的对象 ( 即,在反射中,可以把方法也视为对象 —— 万物皆对象 )

      Method method = cls.getMethod(methodName); // 上面读写到的methodName
      
    • 通过 method 调用方法:即,通过方法对象来实现调用方法

      method.invoke(o);
      // 传统方法是:对象.方法(); 而反射机制是:方法.invoke(对象)
      
    • 这样的话,想要变更调用的方法只需要更改配置文件里的 method=xxx 即可,不用更改源码

      • 但传统的 new 的方法需要直接在源码 hello ——> xxx,要改源码

反射机制

  • 反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息 ( 比如成员变量,构造器,成员方法等等,如刚才用到的 newInstance、getMethod、invoke ),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到

  • 加载完类之后,在堆中就产生了一个 Class 类型的对象 ( 一个类只有一个叫 Class 的对象 ),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为 —— 反射

    • 就像前面的 cls 就是 Class 类型的也叫 Class 的对象
  • Java 程序在计算机有三个阶段:( 编译阶段,运行阶段,加载阶段, )

    • 代码阶段 / 编译阶段。如:Dog 类,内含属性、构造器、方法等 —— ( Javac 编译 ) ——> Dog.class 字节码文件,同样内含属性、构造器、方法等
    • Runtime 运行阶段。new Dog()
    • Class 类阶段 ( 加载阶段 )。在 new 后会从字节码文件通过类加载器 ClassLoad 开始加载字节码文件 ( 此处类加载器就体现了反射 ),生成一个 Class 类对象 ( 在堆里 ),同样内含成员变量 Field[]、构造器Constructor[]、成员方法 Method[] 等 ( 因为可能有多个所以都为数组 )
      • 得到对象后就可以在运行阶段创建并执行对象方法、操作属性等
    • 再回到 Class 类阶段后就生成了 Dog 对象了 ( new 的在堆中 ),该对象知道是属于哪个 Class 对象的 ( 加载 ——> 运行 ——> 加载 ) 相互联系
  • Java 反射机制可以完成

    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时得到任意一个类所具有的成员变量和方法
    • 在运行时调用任意一个对象的成员变量和方法
    • 生成动态代理
  • 反射相关的主要类 ( 类中都把对应的 xx 变成了各个对象 )

    • java.lang.Class:代表一个类,其中 Class 对象表示某个类加载后在堆中的对象

    • java.lang.reflect.Method:代表类的方法,其中 Method 对象表示某个类的方法

    • java.lang.reflect.Field:代表类的成员变量,其中 Field 对象表示某个类的成员变量

      // o是上面实例化的对象实例
      Field nameField = cls.getField("age");
      System.out.println(nameField.get(o)); // 传统常用:对象.方法(..)  此处反射:对象.方法(..对象..)
      // 注意要共有public的才可以,私有和默认的都不行
      // 突出的就是一个“反”字
      
    • java.lang.reflect.Constructor:代表类的构造方法,其中 Constructor 对象表示构造器

      Constructor constructor = cls.getConstructor(); // 括号中可以指定构造器参数类型,此处返回的是无参构造器
      //        sout:public com.zyz.Dog()
      //        如果是要构造器:public Dog(String name){..}的:
      Constructor constructor2 = cls.getConstructor(String.class); // 有形参的构造器
      //        sout:public com.zyz.Dog(java.lang.String)
      

反射的优缺点

  • 优点:可以动态的创建和使用对象 ( 也是框架底层核心 ),使用灵活,没有反射机制,框架技术就失去底层支撑

  • 缺点:使用反射基本是解释执行,对执行速度有影响

    • 证明就先后获取时间做差:System.currentTimeMillis();

    • 普通的 for 循环调用上千万次 new 了的 Dog 类中的 hello 方法

    • 普通的 for 循环调用同上千万次反射的 hello 方法

      Method hello = cls.getMethod("hello"); 
      // for 循环里
      hello.invoke(o);
      
    • 反射时间远远大于 new 方法的调用

  • 反射调用优化 —— 关闭访问检查 ( 能稍加快一下速度,提提效率 )

    • Method 和 Field、Constructor 对象都有 setAccessible() 方法

    • setAccessible 作用是启动和禁用访问安全检查的开关

    • 参数值为 true 表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为 false 则表示反射的对象执行访问检查

      Method hello = cls.getMethod("hello"); 
      hello.setAccessible(true); // 在中间,使得反射调用方法时,取消访问检查
      hello.invoke(o);