zoukankan      html  css  js  c++  java
  • 深入剖析java反射原理

    Java高级之反射

    Class类

    从java世界理解Class

    • 问题一:类和对象的关系?

    • 答曰:类是抽象的概念,它是具有相同属性和方法的一组对象集合,它代表着事物的模板;而对象是能够真正“感觉的到、看得见,摸得着的”具体的实体。对对象的抽象便是,而的实例化结果便是对象

    • 问题二:有个可能不恰当的问法:对象的抽象是类,那类的抽象用什么表示?

      • java API中有个类java.lang.Class,该类是用来描述类的类(比较拗口),为了帮助理解,直接上图:

      image-20200719150049192

      • 对象是具体的实例:比如哈士奇、泰迪、正方形、三角形;
      • 类是对象的抽象:比如Dog类、Shape类;
      • 哈士奇、泰迪同属于狗,具有小狗类型的一些属性,比如吃饭、睡觉等,三角形和正方形同属于形状,有相同的一些属性,比如边长、面积等,那么针对于哈士奇、泰迪,我们选择了Dog来描述,我们可以说:哈士奇、泰迪的类型是Dog类,正方形、三角形的类型是Shape类;
      • 那么请问:Dog类和Shape类的类型是什么类?答案便是Class类,它用来描述类的类型

    从JVM类加载过程理解Class类。

    • 以上图Dog类入手,创建一个Dog类以及一个测试类。

      //Dog类,是用来描述各个品种的狗的的类,
      public class Dog {
          private String name;
          public String getName() {
              return name;
          }
          public void setName(String name) {
              this.name = name;
          }
          public void eat() {
              System.out.println("我的名字是:" + getName() + ",我开始吃饭了!");
          }
      }
      class DogTest{
          public static void main(String[] args) {
              //huskie、teddy是Dog类的具体实例。
              Dog huskie = new Dog();
              huskie.setName("哈士奇");
              huskie.eat();
              Dog teddy = new Dog();
              teddy.setName("泰迪");
              teddy.eat();
          }
      }
      
    • 过程解读:

      1. Dog类经过javac.exe命令以后,会生成Dog.class字节码文件;
      2. 之后使用java.exe对Dog.class字节码文件进行解释执行,即加载到内存中,该过程称为类的加载;
      3. 加载Dog.class类时,JVM为其创建一个Class类型的实例,并将其关联起来。也就是说:当Dog.class加载到内存中时,JVM为其创建了一个Class实例,这个Class实例包含了该class类型的所有完整信息。那么只要能得到某个类的Class实例,那么便可以拿到该类的类名、包名、父类、实现的接口、所有方法、字段等等。

      注意:

      • 类的加载过程中,加载到内存中的类,称之为运行时类,该运行时类,就作为Class的一个实例。也就是说:Class的实例对应着一个运行时的类,它会在内存中缓存一定的时间,在此时间内,我们能通过不同方式获取运行时类。
      • Class实例在JVM中是唯一的,因此无论通过何种方式获取到的Class实例都是同一个实例。
      • 不同的class文件会产生不同的Class实例,加载Dog.class时,JVM会为Dog.class创建一个Class类型的实例, 加载Shape.class时,JVM会为Shape.class创建一个Class类型的实例。

    Class实例与类的实例

    • 方便个人理解,直接上图。

      image-20200719225136458

    • 我们对Dog.class字节码文件进行解释执行,加载到内存中,并且将这些静态数据转换成方法区的运行时的数据结构,之后会在堆中生成一个代表这个类的java.lang.Class对象,用来封装类在方法区内的数据结构,可以作为方法区中类数据的访问入口(好像JDK版本不同,Class对象所在的位置也不同)。

    反射概念及意义

    基本理解

    • 在上面内存加载时创建了Class实例,Class实例上保存了这个类的所有完整信息,那么,通过Class实例获取class信息的方法便称为反射(Reflection);
    • 反射机制就是在程序的运行过程中被允许对程序本身进行操作,它是动态语言的关键;
    • 在程序运行时,系统始终为所有的对象维护一个被称为运行时的类型标识。例如Dog.classDog类的Class类型的实例,Shape.classShapeClass类型的实例。这些Class类型的实例跟踪着每个对象所属的类。这些Class类型的实例保存这些类的完整信息。
    • class类的关系:class是描述类的一个关键字。Class却是保存着运行时信息的类。
    • Class与反射配套使用,因为Class类能够帮助我们在程序运行时分析类,获取运行时类中的值。

    反射能做到的事

    • 拿到Class实例后,我们在程序运行时便可以做到:
      • 判断任意一个对象所属的类以及构造任意一个类的对象;
      • 获取任意一个类所具有的成员变量和方法;
      • 获取泛型信息(会在泛型章节详细分析);
      • 调用任意一个对象的成员变量和方法;
      • 处理注解;
      • 动态代理。
    • 可以拥有Class对象的类型有:
      • primitive type:基本数据类型;
      • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类;
      • interface:接口;
      • []:数组;
      • enum:枚举;
      • annotation:注解@interface;
      • void。

    反射的常用方法

    获取Class类的实例

    • 获取Class类的实例的四种方法,以Dog类为例。
    //方式一:直接调用运行时类的属性:.class;
    Class clazzOne = Dog.class;
    System.out.println("方式一获取到的Class:" + clazzOne);
    //方式二:使用运行时类的对象,调用getClass()方法;
    Dog dog1 = new Dog();
    Class clazzTwo = dog1.getClass();
    System.out.println("方式二获取到的Class:" + clazzTwo);
    //方式三:直接使用Class的静态方法:forName(String classPath);
    Class clazzThree = Class.forName("com.practice.reflect.Dog");
    System.out.println("方式三获取到的Class:" + clazzThree);
    //方式四:使用类的加载器:ClassLoader
    ClassLoader classLoader = DogTest.class.getClassLoader();
    Class clazzFour = classLoader.loadClass("com.practice.reflect.Dog");
    System.out.println("方式四获取到的Class:" + clazzFour);
    //比较获取到的是否是同一个Class
    Boolean bool = (clazzOne == clazzTwo) && (clazzTwo == clazzThree) && (clazzThree == clazzFour);
    System.out.println(bool);
    

    获取类加载器

    • 类加载器有如下几种类型
      • 自定义类加载器 ➡️ 系统类加载器 ➡️ 扩展类加载器 ➡️ 引导类加载器
      • 系统类加载器(System Classloader):负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器;
      • 扩展类加载器(Extension Classloader):负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库;
      • 引导类加载器(Bootstap Classloader):用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取。
    public static void main(String[] args) {
            //拿到DogTest类的类加载器,自定义的类是系统类加载器进行加载
            ClassLoader classLoaderOne = DogTest.class.getClassLoader();
            System.out.println("系统类加载器classLoaderOne:" + classLoaderOne);
            //获取一个系统类加载器
            ClassLoader classLoaderOne1 =  ClassLoader.getSystemClassLoader();
            System.out.println("系统类加载器classLoaderOne1:" + classLoaderOne1);
            System.out.println(classLoaderOne1 == classLoaderOne);
            //系统类加载器可以以流的方式创建一个资源  文件目录在当前src下
            InputStream is = classLoaderOne1.getResourceAsStream("jdbc.properties");
            //调用系统类加载器的getParent():获取扩展类加载器
            ClassLoader classLoaderTwo = classLoaderOne.getParent();
            System.out.println("扩展类加载器classLoaderTwo:" + classLoaderTwo);
            //调用扩展类加载器的getParent():无法获取引导类加载器
            //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
            ClassLoader classLoaderThree = classLoaderTwo.getParent();
            System.out.println("引导类加载器classLoaderThree:" + classLoaderThree);
        }
    

    创建运行时类的对象

    • 获取到Class类的实例后,我们可以创建运行时类的对象;比如通过Dog.class获取到对应的Class实例后,创建一个Dog对象名为Labrador拉布拉多犬。

       //通过Dog.class拿到Class对象后
       Class clazz = Dog.class;
       //使用Class的newInstance()方法创建对象,它实际上是调用了运行时类的空参的构造器
       //使用条件: 1.运行时类必须提供空参的构造器  2.空参的构造器的访问权限得够,通常为public。
       Dog Labrador = (Dog)clazz.newInstance();
       //也可以通过构造器创建  有这样的构造器:Dog(String name)
       Constructor constructor = clazz.getConstructor(String.class);
       Dog d = (Dog)constructor.newInstance("Labrador");
      

    反射各方法梳理

    类的属性

    • 首先拿到Class实例。

      Class clazz = Dog.class;
      
    • 通过Class拿到类的属性

      //获取当前运行时类及其父类中声明为public访问权限的属性
      Field[] fields = clazz.getFields();
      //获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
      Field[] declaredFields = clazz.getDeclaredFields();
      
    • 通过上面拿到的属性进而拿到这个属性的其他信息

      • getModifiers():返回属性的修饰符,是一个int
      • getType():返回字段类型,也是一个Class实例;
      • getName():返回属性名称;
       Field[] fields1 = clazz.getDeclaredFields();
       for (Field field : fields1) {
           //以 private String name; 为例
           //返回权限修饰符  private
           int m = field.getModifiers();
           System.out.print(Modifier.toString(m) + "	");
           //返回该字段的类型 String
           Class s = field.getType();
           System.out.print(s.getName() + "	");
           //返回该字段的名字  name
           String name = field.getName();
           System.out.print(name + "	");
       }
      
    • 拿到指定属性的值,并对指定属性的值进行操作

      Dog teddy = new Dog();
      teddy.setName("泰迪");
      Class c = teddy.getClass();
      Field field = c.getDeclaredField("name");
      //如果不设置 会报错:IllegalAccessException  因为name的属性是private的。
      field.setAccessible(true);
      Object value = field.get(teddy);
      System.out.println("name属性的值为" + value);
      //设置name的值  第一个参数是哪一个类的实例,第二个参数是要设置的值。
      field.set(teddy, "哈士奇");
      System.out.println("修改后的值为" + teddy.getName());
      
    • 方法总结

      1. Field[] getFields():获取所有public的field(包括父类);
      2. Field[] getDeclaredFields():获取当前类的所有field(不包括父类);
      3. Field getField(name):根据字段名获取某个public的field(包括父类);
      4. Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)。

    类的方法

    • 通过Class实例可以获取到方法Method信息

      //getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
      Method[] methods = clazz.getMethods();
      //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
      Method[] methods1 = clazz.getDeclaredMethods();
      //获取某个public的Method(包括父类)
      Method method1 = clazz.getMethod("play");
      //获取当前类的某个Method(不包括父类) 无参
      Method method = clazz.getDeclaredMethod("sleep");
      
    • 通过上面拿到的方法,可以进一步拿到该方法的其他信息。

      • getAnnotations():获取方法声明的注解;
      • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义;
      • getReturnType():返回方法返回值类型;
      • getName():返回方法名称;
      • getParameterTypes():返回方法的参数类型,是一个Class数组;
      • getExceptionTypes():抛出的异常。
      //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
      Method[] methods1 = clazz.getDeclaredMethods();
      for (Method method : methods1) {
          //一、获取该方法声明的注解
          Annotation[] annotations = method.getAnnotations();
          for (Annotation annotation : annotations) {
              System.out.println("方法的注解为:" + annotation);
          }
          //二、获取权限修饰符
          System.out.print(Modifier.toString(method.getModifiers()) + "	");
          //三、获取返回值类型
          System.out.print(method.getReturnType().getName() + "	");
          //四、获取方法名
          System.out.printf(method.getName());
          //五、获取参数列表
          Class[] parameterTypes = method.getParameterTypes();
          //六、获取抛出的异常
          Class[]  exceptionTypes = method.getExceptionTypes();  
      }
      
    • 调用方法

      • Object invoke(Object instance, Object... parameters)调用某个对象的方法,第一个参数是对象实例,即在哪一个对象上调用该方法;第二个参数是参数列表。
      Class clazz = Dog.class;
      //创建运行时类的对象
      Dog dog1 = (Dog)clazz.newInstance();
      //以该方法为例:public String play(String toy),获取指定的方法
      Method method = clazz.getDeclaredMethod("play",String.class);
      //确保当前方法可以访问  可以调用private修饰的方法,
      //不加的话,private方法会报错:IllegalAccessException
      method.setAccessible(true);
      //调用方法:调用方法的invoke,Object invoke(Object instance, Object... parameters) 
      // 参数1:对象实例-->dog1  参数2:形参列表;  调用静态方法时,第一个参数传null。
      //方法的返回值为调用方法的返回值
      Object returnObj = method.invoke(dog1, "篮球");
      System.out.println(returnObj);
      

    类的构造器

    • 通过Class对象,可以拿到当前运行时类的构造信息
      • getConstructor(Class...):获取当前运行类中某个publicConstructor
      • getDeclaredConstructor(Class...):获取当前运行类中某个Constructor
      • getConstructors():获取当前运行类中所有publicConstructor
      • getDeclaredConstructors():获取当前运行类中所有Constructor
    //Dog类的实例化
    //1、使用new关键字
    Dog teddy = new Dog();
    teddy.setName("teddy");
    //2、使用反射的方式
    //2.1:使用的是无参的构造方法;2.2:权限修饰符为public
    Dog dog1 = Dog.class.newInstance();
    dog1.setName("田园犬");
    //3、使用反射调用任意的构造方法  例:public Dog(String name);
    Class clazz = Dog.class;
    //3.1、取当前运行时类中声明为public的构造器
    Constructor[] constructors = clazz.getConstructors();
    for (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    System.out.println();
    //3.2、获取当前运行类中声明的所有构造器的方法
    Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
    for (Constructor declaredConstructor : declaredConstructors) {
        System.out.println(declaredConstructor);
    }
    //3.3、获取当前运行类中的String类型参数的构造方法
    Constructor constructor = clazz.getConstructor(String.class);
    //3.4、保证此构造器是可访问的
    constructor.setAccessible(true);
    //3.5、调用构造方法创建对象
    Dog dog2 = (Dog)constructor.newInstance("哈士奇");
    

    获取类的其他信息

    • 除下以上常用的方法之外,还可以通过Class实例获取该类的其他信息
      • Class getSuperclass()获取运行时的父类;
      • Type getGenericSuperclass()获取带泛型父类;
        • Type[] getActualTypeArguments()获取带泛型的父类的泛型;
      • Class[] getInterfaces():获取当前类实现的所有接口;
      • Package getPackage():获取当前类所在包;
      • Annotation[] getAnnotations():获取运行时类声明的注解。

    总结

    • 反射是框架的灵魂,学好反射才能深入理解框架。

    原创不易,欢迎转载,转载时请注明出处,谢谢!
    作者:潇~萧下
    原文链接:https://www.cnblogs.com/manongxiao/p/13429889.html

  • 相关阅读:
    select()函数用法二
    fcntl函数的用法总结
    O_NONBLOCK与O_NDELAY有何不同?
    struct termios结构体详解
    select()函数用法一
    linux下的struct sigaction
    sigaction 用法实例
    nand flash 的oob 及坏块管理
    wifi两种工作模式
    UBIFS文件系统简介 与 利用mkfs.ubifs和ubinize两个工具制作UBI镜像 (完整理解版本)
  • 原文地址:https://www.cnblogs.com/manongxiao/p/13429889.html
Copyright © 2011-2022 走看看