跳到主要内容

第23章-反射

概述

  • 反射(Reflection),是指在Java程序运行期间,能够动态的获取类、接口、枚举等的属性、方法及其他元信息,然后可以进行相应操作,就是Java的反射机制
  • 类、接口、枚举等具有的所有的特性,像属性、方法、注解、访问修饰符等,都可以通过反射获取
  • 使用反射技术,能使代码更简洁、复用度更高,更方便扩展;常见框架的底层都是通过反射机制实现的,如Spring、MyBatis等
  • Java反射是通过类(或对象)、接口、枚举等获取一个名为Class类的对象进行的

优点

  • 提高了Java程序的灵活性和扩展性,降低了耦合性,提高了自适应能力
  • 允许程序创建和控制任何类的对象,无需提前硬编码目标类

缺点

  • 反射的性能较低,反射主要应用在对灵活性和扩展性要求高的系统框架上。
  • 反射会模糊程序内部的逻辑,从而造成程序的可读性比较差。
  • 突破封装的限制,即使私有的内容也可以通过反射进行操作

反射相关的类

  • java.lang.Class:代表一个类
  • java.lang.reflect.Field:代表类的属性
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Constructor:代表类的构造方法
  • java.lang.reflect.Parameter:代表方法中的参数

获取Class对象

Java代码的三个阶段

  • 源代码(source),此处源代码不是指“.java”文件,而是经过编译器(javac.exe)编译后生成的“.class”字节码文件,这个“*.calss”文件中包含了原始文件中的所有的信息

  • 类对象(class),字节码文件(.class)被类加载器加载到虚拟机中,可以通过“.class”文件获取原始类中的所有的信息,随之被加载的类就产生一个Class类对象(万物皆对象)

  • 运行时(runtime),通过new关键字来创建对象(以类为模板创建对象),或者通过Class对象的newInstance()方法来创建对象

  • 具体如下图

    反射-获取Class对象-Java代码的三个阶段-1

为什么要获取Class对象

  • 字节码文件被加载进入虚拟机后,随之加载的类就产生了一个Class对象,获取Class对象就能获得关于类的属性、方法和构造方法等信息,同时Class对象也是Java反射机制的起源和入口

获取Class对象的方式

  • 获取类(或对象)、接口、枚举等Class类对象方式

    • 方式1

      Class clazz = User.class;
    • 方式2

      User user = new User(1,"张三","male",false,"123456");
      Class clazz = user.getClass();
    • 方式3,【常用】

      Class clazz = Class.forName("com.bjpowernode.demo.User"); 
    • 方式4,了解

      // 获得系统类加载器
      ClassLoader classLoader = ClassLoader.getSystemClassLoader();
      // 通过类加载器来获得Class对象
      Class clazz = classLoader.loadClass("com.bjpoernode.demo.User");
  • 获取Class类对象中的元信息方法

    • 获取类属性列表

      Field[] fields = clazz.getDeclaredFields();
    • 获取类构造方法列表

      Constructor[] constructors = clazz.getDeclaredConstructors();
    • 获取类普通方法列表

      Method[] methods = clazz.getDeclaredMethods();
    • 获取方法参数列表

      Parameter[] parameters = method.getParameters();

Class可以指哪些结构

  • 在Java语言中,Class可以指任意Java类型。通过class属性,我们可以获得基本数据类型、void、数组、枚举、接口、泛型和类等类型的Class对象;具体如下:

    // 获得基本数据类型的Class对象
    Class<Integer> cls01 = int.class;
    System.out.println(cls01);
    // 获得一维数组类型的Class对象
    Class<int[]> cls02 = int[].class;
    System.out.println(cls02);
    // 获得二维数组类型的Class对象
    Class<int[][]> cls03 = int[][].class;
    System.out.println(cls03);
    // 获得void的Class对象
    Class<Void> cls04 = void.class;
    System.out.println(cls04);
    // 获得包装类的Class对象
    Class<Integer> cls05 = Integer.class;
    System.out.println(cls05);
    // 获得自定义类的Class对象
    Class<Tiger> cls06 = User.class;
    System.out.println(cls06);
    // 获得接口的Class对象
    Class<AutoCloseable> cls07 = AutoCloseable.class;
    System.out.println(cls07);
    // 获得泛型的Class对象
    Class<Override> cls08 = Override.class;
    System.out.println(cls08);
    // 获得枚举的Class对象
    Class<ElementType> cls09 = ElementType.class;
    System.out.println(cls09);

类的加载过程

  • 字节码文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些字节码文件呢???系统加载字节码文件主要有三步:装载 -> 连接 -> 初始化

    • 装载(loading),将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成

    • 连接(linking)

      • 验证(Verify),确保加载类的信息符合JVM规范,例如:以0xcafebabe开头表示就是.class字节码文件,就没有安全问题(在notepad++安装HEX-Editor查看验证)
      • 准备(Prepare),正式为静态变量在方法区中开辟存储空间并设置默认值。这里“通常情况”是设置静态变量的默认值,比如我们定义了public static int value = 11,那么value变量在准备阶段设置的初始值就是0,而不是11(初始化阶段才会显示赋值)。特殊情况:比如给value变量加上了fianl关键字public static final int value = 11,那么准备阶段value的值就被赋值为11
      • 解析(Resolve),将虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程(在IDEA安装Jclasslib插件查看)
    • 初始化(initialization)

      • 执行类构造器<clinit>()方法的过程,也就是把编译时期自动收集类中所有静态变量的赋值动作和静态代码块中的赋值语句合并
      • <clinit> ()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成clinit()方法
    • 具体如下图

      反射-类的加载过程-1

类的加载分析

类加载器的作用

  • Java程序被编译器编译之后成为字节码文件(.class文件),当程序第一次需要使用某个类时,虚拟机便会将对应的字节码文件进行加载,从而创建出对应的Class对象
  • 而这个将字节码文件加载到虚拟机内存的过程,这个就是由类加载器(ClassLoader)来完成的

类加载器的分类

  • 虚拟机内部提供了三种类加载器(JDK1.8):启动类加载器(BootstrapClassLoader,也称引导类加载器)、扩展类加载器(ExtensionClassLoader)和系统类加载器(SystemClassLoader,也称应用类加载器),同时我们还可以自定义一个类加载器(UserClassLoader)

  • 有了这些类加载器后,就可以实现不同的类被不同的类加载器加载到内存

  • 用户自定义类加载器(UserClassLoader)的父加载器是系统类加载器(SystemClassLoader),其父加载器是扩展类加载器(ExtensionClassLoader),其父加载器是启动类加载器(BootstrapClassLoader),这里指的是父加载器,并无继承关系

  • 具体如下图

    反射-类的加载分析-1

  • 应用实例

    //获得系统类加载器(SystemClassLoader)
    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
    System.out.println(systemClassLoader); // 输出:sun.misc.Launcher$AppClassLoader@xxx

    //获得扩展类加载器(ExtensionClassLoader)
    ClassLoader extensionClassLoader = systemClassLoader.getParent();
    System.out.println(extensionClassLoader); // 输出:sun.misc.Launcher$ExtClassLoader@xxx

    //获得启动类加载器(BootstrapClassLoader)
    ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
    System.out.println(bootstrapClassLoader); // 输出:null
  • 引导类加载器(Bootstrap ClassLoader)

    • 这个类加载器使用C/C++语言实现的,嵌套在JVM内部,通过Java代码无法获得。 用来加载Java核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
    • 并不继承java.lang.ClassLoader,没有父加载器
    • 处于安全考虑,Bootstrap启动类加载器值加载包名为java、javax、sun开头的类
    • 扩展类加载器和应用程序加载器,并指定他们的父类加载器
  • 扩展类加载器(Extension ClassLoader)

    • 由java语言编写,继承于ClassLoader类,sun.misc.Launcher$ExtClassLoader
    • 父类加载器为启动类加载器
    • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库,如果用户创建jra放在此目录下,也会由扩展类加载器加载
  • 系统类加载器(System ClassLoader)

    • java语言编写,继承于ClassLoader类,sun.smisc.Launcher$AppClassLoader
    • 父类加载器为扩展类加载器
    • 负责加载环境变量classpath或系统属性java.class.path指定路径下的类(加载自定义类)
    • 通过ClassLoader的getSystemClassLoader()方法获取获取到该类加载器
  • 用户自定义加载器(UserClassLoader)

    • 在Java的日常引用程序开发中,类加载器几乎由上述三种类加器相互配合执行的。在必要的时候,开发人员可以自定义类加载器,来制定类的加载方法
    • 体现java语言强大生命力和魅力的关键因素之一,便是java开发者可以自定义类加载器来实现类库的动态加载,可以是本地的jar,也可以是网络上的资源
    • 通过类加载器可以实现非常绝妙的插件机制。类加载器为应用程序提供类一种动态增加新功能的机制。 自定义类加载器能够实现应用隔离和字节码加密等功能
    • 自定义类加载通过需要继承于ClassLoader,其父类加载器为系统加载器
  • 应用实例

    ClassLoader classLoader1 = String.class.getClassLoader();
    System.out.println(classLoader1); // 输出:null --> 启动类加载器

    ClassLoader classLoader2 = DNSNameService.class.getClassLoader();
    System.out.println(classLoader2); // 输出:sun.misc.Launcher$ExtClassLoader@xxx

    ClassLoader classLoader3 = Test01.class.getClassLoader();
    System.out.println(classLoader3); // 输出:sun.misc.Launcher$AppClassLoader@xxx
  • 提问:Class.forName()与ClassLoader.loadClass()的区别?

    • Class.forName():是一个静态方法,根据传递类的全限定名返回一个Class对象。该方法在将Class加载到内存的同时,会进行类的初始化(initialization)
    • ClassLoader.loaderClass():是一个实例化方法,需要一个ClassLoader对象调用。该方法将Class加载到内存的同时,不会进行类的初始化(initialization),直到类第一次被使用

双亲委派模型

  • 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托交给父类加载器,父类加载器又将加载任务向上委托,直到最父类加载器,如果最父类加载器可以完成类加载任务,就成功返回,如果不行就向下传递委托任务,由其子类加载器进行加载;如下图

    反射-类的加载分析-2

  • 双亲委派机制的好处,避免类的俯冲加载,确保类的全局唯一性,同时保护程序安全,防止核心API被随意篡改。例如:如果自己写了一个java.lang.String类就会因为双亲委派机制不能被加载,不会破坏原生的String类的加载;如下图

    反射-类的加载分析-3

【练习】

  1. 练习应用实例内容,完成代码编写

通过反射创建对象

newInstance()方法

  • 在前面的学习中,学习了四种方式来获得Class对象,那么获得的Class对象可以用来做什么呢?我们通过获得的Class对象来调用newInstance()方法,就可以实例化一个对象

  • 应用实例

    • 应用实例1,使用newInstance()方法创建对象

      // Tiger类中的代码
      public class Tiger {
      public Tiger() {
      System.out.println("Tiger类的无参构造方法");
      }
      }
      // 测试类的代码
      public class Test01 {
      public static void main(String[] args) {
      try {
      // 1.获得Tiger类的Class对象
      Class<Tiger> clazz = Tiger.class;
      // 2.通过newInstance()方法来创建对象
      Tiger tiger = clazz.newInstance();
      System.out.println(tiger);
      } catch (InstantiationException e) {
      e.printStackTrace();
      } catch (IllegalAccessException e) {
      e.printStackTrace();
      }
      }
      }
  • 由上执行结果可知,通过Class对象来调用newInstance()方法,则默认就会调用无参构造方法来创建对象。

    【强调】使用newInstance()方法的注意点

    1. 如果该类没有提供无参构造方法,则就会抛出InstantiationException异常。
    2. 如果该类的无参构造方法权限不够,则就会抛出IllegalAccessException异常

    注意:使用Class类的newInstance()方法来创建对象,要求该类必须提供无参构造方法,并且该无参构造方法的权限要足够,因此Class类的newInstance()方法具备很大的局限性。开发中,想要通过反射机制来创建对象,则一般都是使用Constructor类提供的方法来实现

使用反射的经典案例
  • 通过new关键字来创建对象,则这种创建对象的代码是死的,每次只能创建一个固定的对象,代码如下

    Tiger tiger = new Tiger();
  • 想要实现灵活而动态的去创建对象,则就可以通过反射机制来实现。例如:把需要创建对象的类名保存在配置文件中,通过读取配置文件拿到类名,然后根据类名来创建对象,这就必须使用反射机制来实现;代码如下

    • 反射创建对象代码

      public class Test02 {
      public static void main(String[] args) {
      try {
      // 实例化Properties集合
      Properties properties = new Properties();
      // 通过IO流来读取classinfo.properties文件
      InputStream fis = new FileInputStream("classinfo.properties");
      // 加载IO流的数据
      properties.load(fis);
      // 获取配置文件中的类名
      String className = properties.getProperty("className");
      // 通过类名获得Class对象
      Class<?> clazz = Class.forName(className);
      // 通过Class对象调用newInstance()方法来创建对象
      Object obj = clazz.newInstance();
      System.out.println(obj);
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      }
    • 配置文件内容,文件为classinfo.properties

      className=包名.Tiger

【练习】

  1. 练习应用实例内容,完成代码编写

获取类的所有结构

获得类的相关信息

  • 想要获得Class对象对应的类名、包名、父类和实现的接口,则需要使用Class类提供的方法来实现,常见方法如下

    方法名描述
    public String getName()获得完整的类名(带包名的类)
    public String getSimpleName()获得类名(不包含类名)
    public Package getPackage()获得类对应包的Package对象
    public native Class<? super T> getSuperclass()获得该类的父类Class对象
  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类的class对象
    Class<?> clazz = Class.forName("com.reflection.Tiger");
    // 获得带包名的类
    String name = clazz.getName();
    System.out.println(name); // 输出:com.reflection.Tiger
    // 获得类名(不带包名)
    String simpleName = clazz.getSimpleName();
    System.out.println(simpleName); // 输出:Tiger
    // 获得类对应的包
    Package clazzPackage = clazz.getPackage();
    System.out.println(clazzPackage.getName()); // 输出:com.reflection
    // 获得该类的父类
    Class<?> sClass = clazz.getSuperclass();
    System.out.println(sClass.getName()); // 输出:com.reflection.Animal
    // 获得当前类实现的接口
    Class<?>[] interfaces = clazz.getInterfaces();
    for (Class<?> anInterface : interfaces) {
    System.out.println(anInterface.getName());
    }
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    }

获得类的所有属性

  • 想要获得Class对象对应的所有属性(成员变量和静态变量),则需要使用Class类提供的方法来实现,常见方法如下

    方法名描述
    public Field[] getFields()获得当前类和父类的所有公开属性
    public Field[] getDeclaredFields()获得当前类的所有权限属性
  • 应用实例

    public static void main(String[] args) {
    Class<Tiger> clazz = Tiger.class;
    // 获得当前类和父类的公开权限的属性
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
    System.out.println(field.getName());
    }

    System.out.println("-------------------------");

    // 获得当前类的所有权限的属性
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field field : declaredFields) {
    System.out.println(field.getName());
    }
    }
  • 在以上的方法中,涉及到了Field类,此处的Field代表的就是属性(成员变量和静态变量),关于Field类常见的方法如下

    方法名描述
    public String getName()获得属性的名字
    public Class<?> getType()以Class类型的方式返回属性的类型
    public int getModifiers()获取属性的修饰符列表,返回修饰符是一个数字,每个数字是修饰符的代号(配合Modifier类的toString()方法使用)
  • 应用实例

    public static void main(String[] args) {
    Class<Tiger> clazz = Tiger.class;
    // 获得当前类的所有权限的属性
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
    /**
    * 修饰符 十进制 二进制
    * public 1 1
    * private 2 10
    * protected 4 100
    * static 8 1000
    * final 16 10000
    * ......
    */
    // 修饰符
    int modifiers = field.getModifiers();
    System.out.print(Modifier.toString(modifiers) + "\t");
    // 类型
    System.out.print(field.getType().getSimpleName() + "\t");
    // 名字
    System.out.print(field.getName());
    System.out.println();
    }
    }

获得类的所有方法

  • 想要获得Class对象对应的所有方法(成员方法和静态方法),则需要使用Class类提供的方法来实现,常见方法如下

    方法名描述
    public Method[] getMethods()获得当前类和父类的公开方法
    public Method[] getDeclaredMethods()获得当前类所有权限的方法
  • 应用实例

    public static void main(String[] args) {
    Class<Tiger> clazz = Tiger.class;
    // 获得当前类和父类的公开方法
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
    System.out.println(method.getName());
    }

    System.out.println("-----------------");

    // 获得当前类的所有权限的方法
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method method : declaredMethods) {
    System.out.println(method.getName());
    }
    }
  • 在以上的方法中,涉及到了Method类,此处的Method代表的就是方法(成员方法和静态方法),关于Method类常见的方法如下

    方法名描述
    public int getModifiers()获取方法的修饰符列表,返回修饰符是一个数字,每个数字是修饰符的代号(配合Modifier类的toString()方法使用)
    public Class<?> getReturnType()以Class类型的方式来获得返回值类型
    public String getName()获得方法的名字
    public Class<?>[] getParameterTypes() 获得方法的形参类型(形参可能有多个)
  • 应用实例

    public static void main(String[] args) {
    Class<Tiger> clazz = Tiger.class;
    // 获得当前类的所有权限的方法
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method method : declaredMethods) {
    /**
    * 修饰符 十进制 二进制
    * public 1 1
    * private 2 10
    * protected 4 100
    * static 8 1000
    * final 16 10000
    * ......
    */
    // 修饰符
    int modifiers = method.getModifiers();
    System.out.print(Modifier.toString(modifiers) + "\t");
    // 返回值类型
    System.out.print(method.getReturnType() + "\t");
    // 方法名
    System.out.print(method.getName() + "(");
    // 形参列表
    Class<?>[] parameterTypes = method.getParameterTypes();
    for (int i = 0; i < parameterTypes.length; i++) {
    Class<?> parameterType = parameterTypes[i];
    // 如果是最后一个形参,则直接拼接类型
    if (i == parameterTypes.length - 1) {
    System.out.print(parameterType.getSimpleName());
    }
    // 如果不是最后一个形参,则拼接类型加“,”
    else {
    System.out.print(parameterType.getSimpleName() + ", ");
    }
    }
    // 形参拼接完毕,则最后再拼接一个结束小括号
    System.out.print(")");
    System.out.println();
    }
    }

获得类的构造方法

  • 想要获得Class对象对应的所有构造方法,则需要使用Class类提供的方法来实现,常见方法如下

    方法名描述
    public Constructor<?>[] getConstructors()获得当前类公开的构造方法
    public Constructor<?>[] getDeclaredConstructors()获得当前类所有权限的构造方法
  • 应用实例

    public static void main(String[] args) {
    Class<Tiger> clazz = Tiger.class;
    // 获得当前类的公开构造方法
    Constructor<?>[] constructors = clazz.getConstructors();
    for (Constructor<?> constructor : constructors) {
    System.out.println(constructor.getName());
    }

    System.out.println("-----------------------------");

    // 获得当前类所有权限的构造方法
    Constructor<?>[] dConstructors = clazz.getDeclaredConstructors();
    for (Constructor<?> constructor : dConstructors) {
    System.out.println(constructor.getName() );
    }
    }
  • 在以上的方法中,涉及到了Constructor类,此处的Constructor代表的就是构造方法,关于Constructor类常见的方法如下

    方法名描述
    public int getModifiers()获取方法的修饰符列表,返回修饰符是一个数字,每个数字是修饰符的代号(配合Modifier类的toString()方法使用)
    public String getName()获得方法的名字
    public Class<?>[] getParameterTypes()获得方法的形参类型(形参可能有多个)
  • 应用实例

    public static void main(String[] args) {
    Class<Tiger> clazz = Tiger.class;
    // 获得当前类所有权限的构造方法
    Constructor<?>[] dConstructors = clazz.getDeclaredConstructors();
    for (Constructor<?> constructor : dConstructors) {
    /**
    * 修饰符 十进制 二进制
    * public 1 1
    * private 2 10
    * protected 4 100
    * static 8 1000
    * final 16 10000
    * ......
    */
    // 修饰符
    int modifiers = constructor.getModifiers();
    System.out.print(Modifier.toString(modifiers) + "\t");
    // 获得构造方法名
    System.out.print(constructor.getName() + "(");
    // 形参列表
    Class<?>[] parameterTypes = constructor.getParameterTypes();
    for (int i = 0; i < parameterTypes.length; i++) {
    Class<?> parameterType = parameterTypes[i];
    // 如果是最后一个形参,则直接拼接类型
    if (i == parameterTypes.length - 1) {
    System.out.print(parameterType.getSimpleName());
    }
    // 如果不是最后一个形参,则拼接类型加“,”
    else {
    System.out.print(parameterType.getSimpleName() + ", ");
    }
    }
    // 形参拼接完毕,则最后再拼接一个结束小括号
    System.out.print(")");
    System.out.println();
    }
    }

【练习】

  1. 练习应用实例内容,完成代码编写

操作类的所有结构

操作类中的属性

  • 想要操作Class对象对应的某个属性(成员变量和静态变量),则首先应该获得该属性的Field对象,然后再调用Field类提供的方法来操作属性

  • 在Class类中,获得的Field对象方法如下

    方法名描述
    public Field getField(String name)获得某个公开权限的属性(包含父类)
    public Field getDeclaredField(String name)获得当前类的某个属性(任意权限)
  • 在Field类中,操作属性的方法如下

    方法名描述
    public Object get(Object obj)获得obj对象中对应的属性值
    public void set(Object obj, Object value)设置obj对象中对应的属性值
    public void setAccessible(boolean flag)参数值为true,代表允许操作私有属性
    public boolean equals(Object obj)判断两个Field对象是否相等
  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得Tiger类中公开的的name属性
    Field nameField = clazz.getField("name");
    // 通过反射机制来实例化Tiger对象
    Object obj = clazz.newInstance();
    // 给name成员变量赋值
    nameField.set(obj, "ande");
    // 获得name成员变量值
    String name = (String) nameField.get(obj);
    System.out.println(name);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    }
    }
  • 在以上代码中,我们只能操作当前类和父类中的某个公开属性,如果想要操作当前类中某个任意权限的属性(例如私有权限的属性),则就必须通过getDeclaredField(String name)方法来获得该属性的Field对象,然后在调用Field类提供的方法来操作该属性

  • 操作权限不够的属性时(例如私有权限的属性),则还必须调用Field类提供的setAccessible(boolean flag)方法,并且设置该方法的实参为true,这样才能够去操作该私有属性,否则就会抛出IllegalAccessException异常

  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得私有的color属性
    Field colorField = clazz.getDeclaredField("color");
    // 通过反射来实例化Tiger对象
    Object obj = clazz.newInstance();
    // 设置允许操作私有权限的属性
    colorField.setAccessible(true);
    // 给color成员变量赋值
    colorField.set(obj, "pink");
    // 获得color成员变量值
    String color = (String) colorField.get(obj);
    System.out.println(color);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    }
  • 在以上代码中,操作成员变量的时候,我们都明确了该成员变量从属于哪个对象。那么操作静态变量的时候,我们还需要明确静态变量从属于哪个对象吗?答案是否定的,因为静态变量从属于类,而不是从属于哪个对象。因此操作静态变量的时候,我们无需明确静态变量从属于哪个对象,所以操作静态变量绑定的对象就是null即可

  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得私有的classRoom属性
    Field colorField = clazz.getDeclaredField("classRoom");
    // 设置允许操作私有权限的属性
    colorField.setAccessible(true);
    // 给classRoom静态变量赋值
    colorField.set(null, "教室四");
    // 获得classRoom静态变量值
    String color = (String) colorField.get(null);
    System.out.println(color);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    }

【练习】

  1. 练习应用实例内容,完成代码编写

操作类中的方法

  • 想要操作Class对象对应的某个方法(成员方法和静态方法),则首先应该获得该方法的Method对象,然后再调用Method类提供的方法来操作

  • 在Class类中,获得的Method对象方法如下

    方法名描述
    public Method getMethod(String name, Class<?>... parameterTypes)获得公开权限的方法(包含父类),第一个参数为方法名,第二个参数为形参类型。
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)获得当前类的某个方法(任意权限)。第一个参数为方法名,第二个参数为形参类型。
  • 在Method类中,操作方法的方式如下

    方法名描述
    public Object invoke(Object obj, Object... args)调用某个方法,第一个参数为绑定的对象,第二个参数为传递的实参值,返回值为调用方法返回的结果(返回结果为null,代表没有返回值)。
    public void setAccessible(boolean flag)参数值为true,代表允许调用私有方法。
  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类对应的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得公开的eat(String)方法
    Method eatMethod = clazz.getMethod("eat", String.class);
    // 通过反射机制来创建Tiger对象
    Object obj = clazz.newInstance();
    // 调用eat()方法,并获得返回值
    // 注意:如果invoke()方法的返回值为null,则代表该方法没有返回值
    Object value = eatMethod.invoke(obj, "小明");
    System.out.println(value);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    }
  • 在以上代码中,我们只能调用当前类和父类中的某个公开方法,如果想要调用当前类中某个任意权限的方法(例如私有权限的方法),则就必须通过getDeclaredMethod(String name, Class<?>... parameterTypes)方法来获得该方法的Method对象,然后在调用Method类提供的方法来调用

  • 操作权限不够的方法时(例如私有权限的方法性),则还必须调用Method类提供的setAccessible(boolean flag)方法,并且设置该方法的实参为true,这样才能够去调用该私有方法,否则就会抛出llegalAccessException异常

  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类对应的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得私有的show(String, int)方法
    Method showMethod = clazz.getDeclaredMethod("show", String.class, int.class);
    // 通过反射机制来创建Tiger对象
    Object obj = clazz.newInstance();
    // 设置私有的成员方法可以调用
    showMethod.setAccessible(true);
    // 调用show()成员方法,并获得返回值
    // 注意:如果invoke()方法的返回值为null,则代表该方法没有返回值
    Object value = showMethod.invoke(obj, "小明", 18);
    System.out.println(value);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    }
  • 在以上代码中,调用成员方法的时候,我们都明确了该成员方法从属于哪个对象。那么操作静态方法的时候,我们还需要明确静态方法从属于哪个对象吗?答案是否定的,因为静态方法从属于类,而不是从属于哪个对象。因此调用静态方法的时候,我们无需明确静态方法从属于哪个对象,所以操作静态变量绑定的对象就是null即可

  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类对应的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得私有的sleep(String)方法
    Method showMethod = clazz.getDeclaredMethod("sleep", String.class);
    // 设置私有的静态方法可以调用
    showMethod.setAccessible(true);
    // 调用sleep()静态方法,并获得返回值
    // 注意:如果invoke()方法的返回值为null,则代表该方法没有返回值
    Object value = showMethod.invoke(null, "小明");
    System.out.println(value);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    }

【练习】

  1. 练习应用实例内容,完成代码编写

操作类中的构造方法

  • 想要获得Class对象中的某个构造方法,然后再通过构造方法来创建对象,则首先应获得该构造方法的Constructor对象,然后再调用Constructor类提供的方法来实例化对象

  • 在Class类中,获得的Constructor对象方法如下

    方法名描述
    public Constructor<T> getConstructor(Class<?>... parameterTypes)获得公开权限的构造方法,参数为构造方法的形参类型
    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)获得任意权限的构造方法,参数为构造方法的形参类型
  • 在Constructor类中,操作方法的方式如下

    方法名描述
    public T newInstance(Object ... initargs)调用指定的构造方法来创建对象,形参为构造方法需要传递的参数
    public void setAccessible(boolean flag)参数值为true,代表允许操作私有的构造方法
  • 应用实例

    public static void main(String[] args) {
    try {
    // 获得Tiger类对应的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得公开权限的有参构造方法
    Constructor<?> constructor = clazz.getConstructor(String.class);
    // 调用有参构造方法来创建对象
    Tiger tiger = (Tiger) constructor.newInstance("母老虎");
    System.out.println(tiger);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    }
  • 在以上代码中,我们只能操作类中某个公开构造方法,如果想要操作类中某个任意权限的构造方法(例如私有权限的构造方法),则就必须通过getDeclaredConstructor(Class<?>... parameterTypes)方法来获得该构造方法的Constructor对象,然后在调用Constructor类提供的方法来创建对象

  • 操作权限不够的构造方法时(例如私有权限的方法性),则还必须调用Constructor类提供的setAccessible(boolean flag)方法,并且设置该方法的实参为true,这样才能够去调用该私有权限的构造方法,否则就会抛出IllegalAccessException异常

  • 应用实例,通过私有构造方法来创建对象

    public static void main(String[] args) {
    try {
    // 获得Tiger类对应的Class对象
    Class<?> clazz = Class.forName("com.ande.reflection.Tiger");
    // 获得私有权限的有参构造方法
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, double.class);
    // 设置允许操作私有权限的构造方法
    constructor.setAccessible(true);
    // 调用有参构造方法来创建对象
    Tiger tiger = (Tiger) constructor.newInstance("母老虎", 100.0);
    System.out.println(tiger);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    }
  • 通过以上的方法,我们就能通过任意权限和任意参数的构造方法来创建对象,这中方式比通过Class来调用newInstance()方法灵活很多,因此开发中我们常用Constructor类创建对象

  • 【思考】在JDK17版本中,我们是否能够操作JDK中私有的属性、方法和构造方法呢?

反射中泛型的使用【扩展,了解】

概述

  • Java的泛型是伪泛型,只是在编译的时候进行类型检查,加载类时为了JDK1.4和JDK1.5都能兼容同一个类加载器,因此加载类时所有的泛型信息都会被擦除,所以程序在运行时无法使用反射获取泛型的类型。比如对于泛型类List<String>,在编译的时候会检测所有加入到list里面的对象是否是String类型,运行程序时的对应的Class对象中是不会有泛型信息的
  • 在JDK1.5后Signature属性被增加到了Class文件规范中,它是一个可选的定长属性,可以出现在类、字段表和方法表结构的属性表中。在JDK1.5中大幅度增强了Java语言的语法,在此之后,任何类、接口、构造方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Singature属性会为它记录泛型签名信息。Signature属性就是为了弥补擦除法的缺陷而增设的,Java可以通过反射获得泛型类型,最终的数据来源也就是这个属性
  • 在实际开发中,获取当前类的泛型是没有意义的(类的泛型要在创建对象的时候明确),因此开发中常用于获得父类的泛型、接口的泛型、属性的泛型、方法形参的泛型、方法返回值的泛型和构造方法形参的泛型等等,相关的获取方式在本章节了解即可。

获取类的泛型信息

  • 应用实例,获得父类中的泛型

    /**
    * 父类
    */
    public class Animal<E, F> { }
    /**
    * 子类
    */
    public class Tiger extends Animal<String, Integer> { }
    /**
    * 测试类
    */
    public class Test01 {
    public static void main(String[] args) {
    // 获得Tiger类对应的Class对象
    Class<Tiger> clazz = Tiger.class;
    // 获得父类的泛型信息
    Type superType = clazz.getGenericSuperclass();
    // 判断superType对象是否为ParameterizedType接口的实例
    if (superType instanceof ParameterizedType) {
    // 把superType对象强转为ParameterizedType
    ParameterizedType type = (ParameterizedType) superType;
    // 获得type对象中的所有泛型
    Type[] arguments = type.getActualTypeArguments();
    // 输出arguments数组中的所有泛型
    for (Type argument : arguments) {
    // 此处依次输出“java.lang.String”和“java.lang.Integer”
    System.out.println(argument.getTypeName());
    }
    }
    }
    }
  • 应用实例,获得接口中的泛型

    /**
    * 接口
    */
    public interface Flyable<E, F>{ }
    /**
    * 子类
    */
    public class Tiger implements Flyable<String, Integer> { }
    /**
    * 测试类
    */
    public class Test01 {
    public static void main(String[] args) {
    // 获得Tiger类对应的Class对象
    Class<Tiger> clazz = Tiger.class;
    // 获得接口中的泛型(一个类可以实现多个接口)
    Type[] genericInterfaces = clazz.getGenericInterfaces();
    // 遍历genericInterfaces数组,获得所有接口中的泛型
    for (Type genericInterface : genericInterfaces) {
    // 判断genericInterface对象是否为ParameterizedType接口的实例
    if (genericInterface instanceof ParameterizedType) {
    // 把superType对象强转为ParameterizedType
    ParameterizedType type = (ParameterizedType) genericInterface;
    // 获得type对象中的所有泛型
    Type[] arguments = type.getActualTypeArguments();
    // 输出arguments数组中的所有泛型
    for (Type argument : arguments) {
    // 此处依次输出“java.lang.String”和“java.lang.Integer”
    System.out.println(argument.getTypeName());
    }
    }
    }
    }
    }

获得类中组成的泛型

  • 应用实例,获得属性中的泛型

    /**
    * Tiger类
    */
    public class Tiger {
    private Map<String, Integer> map;
    }
    /**
    * 测试类
    */
    public class Test01 {
    public static void main(String[] args) {
    try {
    Class<Tiger> clazz = Tiger.class;
    // 获得私有的map成员变量
    Field mapField = clazz.getDeclaredField("map");
    // 获得mapField对象中的泛型信息
    Type genericType = mapField.getGenericType();
    // 判断genericType对象是否为ParameterizedType接口的实例
    if (genericType instanceof ParameterizedType) {
    // 把genericType对象强转为ParameterizedType
    ParameterizedType type = (ParameterizedType) genericType;
    // 获得type对象中的所有泛型
    Type[] arguments = type.getActualTypeArguments();
    // 输出arguments数组中的所有泛型
    for (Type argument : arguments) {
    // 此处依次输出“java.lang.String”和“java.lang.Integer”
    System.out.println(argument.getTypeName());
    }
    }
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    }
    }
    }
  • 应用实例,获得方法形参的类,获得Tiger类中方法形参的泛型

    public class Tiger {
    private void argsTest(List<String> list) {}
    }
    /**
    * 测试类
    */
    public class Test01 {
    public static void main(String[] args) {
    try {
    Class<Tiger> clazz = Tiger.class;
    // 获得私有的agrsTest方法
    Method method = clazz.getDeclaredMethod("argsTest", List.class);
    // 获得method对象中形参的泛型信息
    Type[] parameterTypes = method.getGenericParameterTypes();
    // 遍历parameterTypes数组,获得所有形参的泛型信息
    for (Type parameterType : parameterTypes) {
    // 判断parameterType对象是否为ParameterizedType接口的实例
    if (parameterType instanceof ParameterizedType) {
    // 把genericType对象强转为ParameterizedType
    ParameterizedType type = (ParameterizedType) parameterType;
    // 获得type对象中的所有泛型
    Type[] arguments = type.getActualTypeArguments();
    // 输出arguments数组中的所有泛型
    for (Type argument : arguments) {
    // 此处依次输出“java.lang.String”
    System.out.println(argument.getTypeName());
    }
    }
    }
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }
    }
    }
  • 应用实例,获得方法返回值的泛型

    /**
    * Tiger类
    */
    public class Tiger {
    private Map<String, Integer> returnTest() {
    return null;
    }
    }
    /**
    * 测试类
    */
    public class Test01 {
    public static void main(String[] args) {
    try {
    Class<Tiger> clazz = Tiger.class;
    // 获得私有的returnTest()方法
    Method method = clazz.getDeclaredMethod("returnTest");
    // 获得method对象中返回值类型的泛型信息
    Type returnType = method.getGenericReturnType();
    // 判断returnType对象是否为ParameterizedType接口的实例
    if (returnType instanceof ParameterizedType) {
    // 把genericType对象强转为ParameterizedType
    ParameterizedType type = (ParameterizedType) returnType;
    // 获得type对象中的所有泛型
    Type[] arguments = type.getActualTypeArguments();
    // 输出arguments数组中的所有泛型
    for (Type argument : arguments) {
    // 此处依次输出“java.lang.String”和“java.lang.Integer”
    System.out.println(argument.getTypeName());
    }
    }
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }
    }
    }
  • 应用实例,获得构造方法的泛型

    /**
    * Tiger类
    */
    public class Tiger {
    public Tiger(Map<String, Integer> map) { }
    }
    /**
    * 测试类
    */
    public class Test01 {
    public static void main(String[] args) {
    try {
    Class<Tiger> clazz = Tiger.class;
    // 获得Tiger类的有参构造方法
    Constructor<Tiger> constructor = clazz.getConstructor(Map.class);
    // 获得构造方法中形参的泛型
    Type[] parameterTypes = constructor.getGenericParameterTypes();
    // 遍历parameterTypes数组,获得所有形参的泛型信息
    for (Type parameterType : parameterTypes) {
    // 判断parameterType对象是否为ParameterizedType接口的实例
    if (parameterType instanceof ParameterizedType) {
    // 把genericType对象强转为ParameterizedType
    ParameterizedType type = (ParameterizedType) parameterType;
    // 获得type对象中的所有泛型
    Type[] arguments = type.getActualTypeArguments();
    // 输出arguments数组中的所有泛型
    for (Type argument : arguments) {
    // 此处依次输出“java.lang.String”和“java.lang.Integer”
    System.out.println(argument.getTypeName());
    }
    }
    }
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }
    }
    }

【练习】

  1. 练习应用实例内容,完成代码编写

反射的实战案例

榨果汁的案例

  • 需求:在不改变程序代码的前提下,来创建出继承于Fruit抽象类的任意水果对象(Apple、Banana、Orange等),然后调用榨汁机(Juicer)的方法开启榨汁(squeeze)操作

  • 要求:使用配置文件+反射来实现

  • 第一步:定义Fruit抽象类及其实现类

    /**
    * 水果的父类
    */
    public abstract class Fruit {
    /**
    * 榨果汁的方法
    */
    public abstract void squeeze();
    }
    /**
    * 苹果类
    */
    public class Apple extends Fruit {
    @Override
    public void squeeze() {
    System.out.println("榨出一杯苹果汁儿");
    }
    }
    /**
    * 香蕉类
    */
    public class Banana extends Fruit {
    @Override
    public void squeeze() {
    System.out.println("榨出一杯香蕉汁儿");
    }
    }
    /**
    * 橘子类
    */
    public class Orange extends Fruit {
    @Override
    public void squeeze() {
    System.out.println("榨出一杯橘子汁儿");
    }
    }
  • 第二步:定义榨汁机类,并提供榨汁的方法

    /**
    * 榨汁机类
    */
    public class Juicer {
    /**
    * 开始榨果汁
    * @param fruit 水果
    */
    public void run(Fruit fruit) {
    fruit.squeeze();
    }
    }
  • 第三步:创建配置文件,并在配置文件中明确好类名

    className=包名.Orange
  • 第四步:读取配置文件中的信息,然后通过反射创建水果对象并开始榨汁

    public class Test01 {
    public static void main(String[] args) {
    try {
    // 获得资源管理器,此处需要省略配置文件的后缀
    ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
    String className = bundle.getString("className");
    // 通过反射来实例化水果对象
    Class<?> clazz = Class.forName(className);
    Fruit fruit = (Fruit) clazz.newInstance();
    // 创建榨汁机对象,并开启榨汁
    new Juicer().run(fruit);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    }
    }

模拟简单框架

  • 需求:在不改变程序代码的前提下,来创建出任意类的对象,并通过该对象来调用指定方法

  • 要求:使用配置文件+反射来实现

  • 第一步:定义配置文件中的类,并在类中定义要调用的方法

    /**
    * 老虎类
    */
    public class Tiger {
    public void eat() {
    System.out.println("老虎正在吃肉...");
    }
    }
    /**
    * 学生类
    */
    public class Student {
    public String show() {
    System.out.println("执行了学生类的show()方法");
    return "俺正在学习Java语言...";
    }
    }
  • 第二步:创建配置文件,并在配置文件中明确好类名和方法;后面可以分别银的成Student类和show方法

    className=包名.Tiger
    methodName=eat
  • 第三步:读取配置文件中的信息,然后通过反射创建对象并调用方法

    public class Test01 {
    public static void main(String[] args) {
    try {
    // 创建IO流,用于读取classinfo.properties文件中的内容
    InputStream fis = new FileInputStream("classinfo.properties");
    // 实例化Properties集合,用于保存配置文件中的内容
    Properties properties = new Properties();
    // 加载配置文件中的内容
    properties.load(fis);
    // 读取配置文件中的类名和方法名
    String userName = properties.getProperty("className");
    String methodName = properties.getProperty("methodName");
    // 通过反射来创建对象
    Class<?> clazz = Class.forName(userName);
    Constructor<?> constructor = clazz.getDeclaredConstructor();
    Object obj = constructor.newInstance();
    // 通过反射来调用方法
    Method method = clazz.getDeclaredMethod(methodName);
    Object value = method.invoke(obj);
    System.out.println("调用方法返回的结果:" + value);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

【练习】

  1. 练习应用实例内容,完成代码编写