zoukankan      html  css  js  c++  java
  • 关于反射的杂谈

    一.什么是反射机制

      反射使用的前提条件:需要得到字节码的Class类的实例对象,字节码是代码编译后生成的.class文件,Class类用于表示.class文件(字节码);

      Class类的实例对象表示正在运行的 Java 应用程序中的类和接口;

    注意:枚举是一种类,注解是一种接口;

      反射指程序在运行状态可以动态加载,探知和使用编译期间完全未知的类;对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用它的任意一个方法和属性;

      

      反射常用对象:

      • Class
        • Class类的实例表示正在运行的Java应用程序中的类和接口;
      • Constructor
        • 关于类的单个构造方法的信息以及对它的访问权限;    
      • Field
        • Field提供有关类或接口的单个字段的信息,以及对它的动态访问权限;    
      • Method      
        • Method提供关于类或接口上单独某个方法的信息;    

      

      每个类被加载进入内存之后(将.class文件加载进内存),系统就会为该类生成一个对应的java.lang.Class类的实例对象,通过该Class类的实例对象就可以访问到JVM中的这个类;

      

      反射的另一种形象的说法:加载完类之后,在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构;

     

      反射跟内省两者很像,但它们的含义不一样:

    1. 内省用于在运行时检测某个对象的类型和其包含的属性;

    2. 反射用于在运行时检测和修改某个对象的结构及其行为;

      从它们的定义可以看出,内省是反射的一个子集。有些语言支持内省,但并不支持反射,如C++,如下图;

           

     

      内省示例:instanceof运算符用于检测某个对象是否属于特定的类

    if (obj instanceof Dog) {
        Dog d = (Dog) obj;
        d.bark();
    }
    

      

     

      反射示例:Class.forName()方法可以通过类或接口的名称(一个字符串或完全限定名)来获取对应的Class对象,forName方法会触发类的初始化;

    // 使用反射
    Class<?> c = Class.forName("classpath.and.classname");
    Object dog = c.newInstance();
    Method m = c.getDeclaredMethod("bark", new Class<?>[0]);
    m.invoke(dog);
    

      

      在Java中,反射更接近于内省,因为你无法改变一个对象的结构。虽然一些API可以用来修改方法和属性的可见性,但并不能修改结构;

     

    二.为何需要反射

      在刚开始学习JDBC时,写第一行代码是这样的:

    Class.forName("com.mysql.jdbc.Driver");
    

      

      这段代码是用于加载MySQL的JDBC驱动的;或许有人想说可以用另外一种方式加载驱动:

    DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    

      

      通过查看MySQL驱动的源码(5.1版本的)可以看到Driver类的内部实现;

      如果使用上面第二种方式加载驱动会导致驱动被注册两次,这会使得程序过度依赖于mysql的api,脱离的mysql的开发包,程序则无法编译;如果使用上面第一种方式加载驱动,驱动只会被加载一次,不需要依赖具体的驱动,灵活性高;

    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        //
        // Register ourselves with the DriverManager
        //
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    ​
        /**
         * Construct a new driver and register it with DriverManager
         * 
         * @throws SQLException
         *             if a database error occurs.
         */
        public Driver() throws SQLException {
            // Required for Class.forName().newInstance()
        }
    }
    

      

      而Class.forName(...)是返回与带有给定字符串名的类或接口相关联的 Class 对象,Class.forName(className)实际上是调用Class.forName(className,true, this.getClass().getClassLoader()),第二个参数是指Class被loading后是不是必须被初始化,可以看出,使用Class.forName(className)加载类时则已初始化;

      也就是当执行Class.forName(...),JVM会执行该类的静态代码块,静态代码块是和类绑定的,该类装载成功就表示执行了该类的静态代码块;

     

      那么在初始化一个类生成一个实例对象的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,最主要有什么区别(参考来源)?

      1.它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类;

      2.那么为什么会有两种创建对象方式?

      这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想;Java中工厂模式经常使用newInstance()方法来创建对象,因此从为什么要使用工厂模式上可以找到具体答案;

    class c = Class.forName(“Example”);  
    factory = (ExampleInterface)c.newInstance();  
    

      

      其中ExampleInterface是Example的接口,可以写成如下形式:

    String className = "Example";  
    class c = Class.forName(className); 
    factory = (ExampleInterface)c.newInstance();  
    

      

      进一步可以写成如下形式:

    //从配置文件中获得字符串
    String className = readfromXMlConfig;
    class c = Class.forName(className);  
    factory = (ExampleInterface)c.newInstance();  
    

      

      上面代码已经不存在Example的类名称,它的优点是无论Example类怎么变化,上述代码不变,甚至可以更换Example的兄弟类Example2 , Example3 , Example4……,只要他们继承ExampleInterface就可以;但使用newInstance()方法的时候,就必须保证:这个类已经加载;

     

      而完成上面两个步骤的正是Class类的静态方法forName所完成的,forName这个静态方法调用了启动类加载器,即加载 java API的那个加载器; ​ 现在可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class类的加载方法加载某个类,然后实例化;这样分步的好处是显而易见的;在调用Class类的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段;

      newInstance与new的区别:

    newInstance: 弱类型,低效率,只能调用无参构造;

    new: 强类型,相对高效,能调用任何public构造;

     

      现在再看看加载MySQL驱动那行代码,如果使用DriverManager.registerDriver(new com.mysql.jdbc.Driver())来加载,那样的话会向DriverManager加载驱动两次,Class.forName(...)会让JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段,所以在这里使用newInstance()跟DriverManager.registerDriver(new com.mysql.jdbc.Driver())效果是一样的都是加载两次;

      换另一方面说,DriverManager.registerDriver(new com.mysql.jdbc.Driver())这种方式的耦合度太高,如果项目中没有导入jar包,就会在编译期间报错;而Class.forName()这种则不会;如果使用第一种方式加载驱动,那么考虑下oracle和mysql 类名不一样,用具体类名更换麻烦;

     

    三.一些扯谈

      使用反射加载类,就好比应用程序不加载动态库模块,应用的所有模块都是在一个工程的源码中,如果某个模块需要修改,那么整个工程都需要重新编译,而应用使用了动态库加载,则会避免这些问题,当然这是扯的有点远了;

     

    参考:

    http://ju.outofmemory.cn/entry/63866

    https://blog.csdn.net/fengyuzhengfan/article/details/38086743

     

  • 相关阅读:
    java方法参数传值传引用的一点看法
    Oracle触发器介绍
    CASE WHEN
    group by ,order by ,having
    Java中使用正则表达式
    Oracle 9i 分析函数参考手册
    ORACLE round 与 trunc 的区别
    oracle的默认表名长度(30)
    order by 使用索引的情况
    解析oracle的ROWNUM 作者: chen_liang
  • 原文地址:https://www.cnblogs.com/coder-zyc/p/10702665.html
Copyright © 2011-2022 走看看