深入剖析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

原文地址:https://www.cnblogs.com/manongxiao/p/13429889.html