Java反射基础篇【简单原理和常用API的使用】

Java反射,是框架设计的灵魂。

一、概述

1.1 什么是反射?

将类的各个部分封装成其他对象,这就是反射。有点抽象,不太好理解,我们画个图理解一下。
在这里插入图片描述

  • 第一个阶段-源代码阶段

定义一个类文件Dog.java,经过javac编译之后生成Dog.class文件,这两个文件都在硬盘上存储。

这个阶段我们可以称之为源代码阶段

  • 第二个阶段-Class类对象阶段

通过类加载器,可以把这个Dog.class字节码文件加载到内存里,但是内存里怎么去描述这个字节码文件呢?在java里万物皆对象,java定了一种Class类对象来描述所有的字节码文件,抽象成共同的特征,比如成员变量,构造方法,成员方法。

在Class类对象中,把成员变量都封装成Field[] fields,把构造方法都分封装成Constructor[] constructors, 把成员方法都封装成Method[] methods。之所以使用数组是因为一般都有多个。基于此,提供出了各式各样的API用来实现只有一个类该具有的操作,其中成员变量可以用来获取和设置值,构造方法可以用来创建对象,成员方法可以来运行和执行。

这个阶段我们称之为Class类对象阶段

  • 第三个阶段-运行时阶段

如直接new出来或者通过反射生成具体对象之后,这时对象已经“运行”在内存中

这个阶段我们称之为运行时阶段

1.2 反射有什么好处?

  • 可以在程序运行过程中,操作这些对象,动态获取信息以及动态调用对象方法,这也是为什么称之为框架的灵魂
  • 可以解耦,降低程序的紧密连接程度

二、获取字节码Class对象的三种方式

反射是指将一个类的各个部分封装成其他对象,你如何来获取要操作的这个Class类对象,是使用反射的第一步。
具体有三种方式

  1. Class.forName(“全类名”):将字节码文件加载进内存,返回class对象,对应源代码阶段
  2. 类名.class:通过类型的属性class直接获取,对应Class类对象阶段
  3. 对象.getClass():已经有了对象,通过Object中的getClass()方法获取,对应运行时阶段。

与之对应的是上面所讲的三个阶段,如下所示
在这里插入图片描述
直接看个例子:

public class ReflectDemo1 {
  /**
   * 1. Class.forName(“全类名”):将字节码文件加载进内存,返回class对象. 
   * 2. 类名.class:通过类型的属性class直接获取. 
   * 3. 对象.getClass():已经有了对象,通过Object中的getClass()方法获取.
   */
  public static void main(String[] args) throws ClassNotFoundException {
    // 1.Class.forName(“全类名”)
    Class cls1 = Class.forName("reflect.Dog");
    System.out.println(cls1);

    // 2. 类名.class
    Class cls2 = Dog.class;
    System.out.println(cls2);

    // 3. 对象.getClass()
    Dog dog = new Dog();
    Class cls3 = dog.getClass();
    System.out.println(cls3);

    // 比较三个对象
    System.out.println(cls1 == cls2);
    System.out.println(cls1 == cls3);
    System.out.println(cls2 == cls3);
  }
}

运行结过如下:
在这里插入图片描述

这里比较了一下三个对象,发现这三个对象的内存地址都是一样的,说明如下重要结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的class对象都是同一个。

进一步的这三种获取字节码Class对象的方式,具体常用什么场景下。

  1. Class.forName(“全类名”):多用于配置文件,将类名定义在配置文件中,读取文件,加载类。
  2. 类名.class:多用于参数的传递。
  3. 对象.getClass():多用于对象的获取字节码。

三、Class对象功能详解

3.1 概述

Class是一个位于java.lang包下面的一个类,在Java中每个类实例都有对应的Class对象。类对象是由Java虚拟机(JVM)自动构造的。

Class的方法比较多,主要还是以获取为主,对应上面讲的内容,主要是获取成员变量,获取构造器,获取成员方法。常用方法如下所示:

获取功能

  1. 获取成员变量们

    • Field[] getFields() :返回所有public的成员变量
    • Field getField(String name) :返回指定名称的public的成员变量
    • Field[] getDeclaredFields() :返回所有的成员变量
    • Field getDeclaredField(String name) :返回指定名称的成员变量
  2. 获取构造方法们

    • Constructor<?>[] getConstructors() :返回所有public的构造函数
    • Constructor getConstructor(Class<?>… parameterTypes) :返回指定参数类型的的public的构造函数
    • Constructor<?>[] getDeclaredConstructors() :返回所有构造函数
    • Constructor getDeclaredConstructor(Class<?>… parameterTypes) :返回指定参数类型的构造函数
  3. 获取成员方法们

    • Method[] getMethods() :返回所有public的成员方法
    • Method getMethod(String name, Class<?>… parameterTypes) :返回指定参数类型的的public的成员方法
    • Method[] getDeclaredMethods() :返回所有成员方法,不包括继承的方法
    • Method getDeclaredMethod(String name, Class<?>… parameterTypes) :返回指定参数类型的成员方法
  4. 获取其他

    • String getName() :返回此类的完整名称
    • Package getPackage() :返回此类的包
    • String getSimpleName() :返回此类的简单名称
    • ···

判断功能:

  • boolean isAnnotation() :是否是注释类型
  • boolean isArray() :是否是数组
  • boolean isEnum() :是否是枚举类型
  • ···

3.2 获取Field详解

3.2.1 获取Field举例说明

首先定义一个Dog类,这里包含了共有和私有的成员变量、构造函数和成员方法。


public class Dog {

  private String name;
  public int age;
  public String type;

  /**
   * Instantiates a new Dog.
   *
   * @param name the name
   * @param age the age
   */
  public Dog(String name, int age, String type) {
    this.name = name;
    this.age = age;
    this.type = type;
  }

  /** Instantiates a new Dog. */
  public Dog() {}

  /**
   * Gets name.
   *
   * @return the name
   */
  public String getName() {
    return name;
  }

  /**
   * Sets name.
   *
   * @param name the name
   */
  private void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", type='" + type + '\'' + '}';
  }
}

对应的四种获取成员变量的方法使用实例如下。

public class RelectDemo2 {

  public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
    Class cls1 = Class.forName("reflect.Dog");
    
    // Field[] getFields() :返回所有public的成员变量
    System.out.println("-------Field[] getFields() :返回所有public的成员变量-------");
    Field[] fields = cls1.getFields();
    for (Field f : fields) {
      System.out.println(f);
    }
    
    // Field getField(String name) :返回指定名称的public的成员变量
    System.out.println("-------Field getField(String name) :返回指定名称的public的成员变量-------");
    Field filed = cls1.getField("type");
    System.out.println(filed);

    // Field[] getDeclaredFields() :返回所有的成员变量
    System.out.println("-------Field[] getDeclaredFields() :返回所有的成员变量-------");
    Field[] declaredFields = cls1.getDeclaredFields();
    for (Field f : declaredFields) {
      System.out.println(f);
    }
    
    // Field getDeclaredField(String name) :返回指定名称的成员变量
    System.out.println("-------Field getDeclaredField(String name) :返回指定名称的成员变量-------");
    Field declaredfiled = cls1.getDeclaredField("name");
    System.out.println(declaredfiled);
  }
}

对应的运行结果如下
在这里插入图片描述

3.2.2 获取了Field之后有什么用?

但是获取了有什么用的,对于成员变量来说,无非是两种作用获取值和设置值,具体的api可以看Field方法,这里举一个简单例子说明一下即可,详见代码。
涉及到的API

//设置值,其中obj为具体的Dog实例对象,value是想要设置的值
void set(Object obj,Object value)
//获取值,其中obj是具体的Dog实例对象
get(Object obj)

使用代码如下:


public class RelectDemo2 {

  public static void main(String[] args)
      throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    Class cls1 = Class.forName("reflect.Dog");

    // Field[] getFields() :返回所有public的成员变量
    System.out.println("-------Field[] getFields() :返回所有public的成员变量-------");
    Field[] fields = cls1.getFields();
    for (Field f : fields) {
      System.out.println(f);
    }

    // Field getField(String name) :返回指定名称的public的成员变量
    System.out.println("-------Field getField(String name) :返回指定名称的public的成员变量-------");
    Field field = cls1.getField("type");
    System.out.println(field);

    // 获取成员变量的值
    Dog dog1 = new Dog();
    Object value1 = field.get(dog1);
    System.out.println(value1);

    field.set(dog1, "二哈");
    value1 = field.get(dog1);
    System.out.println(value1);

    // Field[] getDeclaredFields() :返回所有的成员变量
    System.out.println("-------Field[] getDeclaredFields() :返回所有的成员变量-------");
    Field[] declaredFields = cls1.getDeclaredFields();
    for (Field f : declaredFields) {
      System.out.println(f);
    }

    // Field getDeclaredField(String name) :返回指定名称的成员变量
    System.out.println("-------Field getDeclaredField(String name) :返回指定名称的成员变量-------");
    Field declaredfiled = cls1.getDeclaredField("name");
    System.out.println(declaredfiled);

    // 获取成员变量的值
    Dog dog2 = new Dog();
    Object value2 = declaredfiled.get(dog2);
    System.out.println(value2);

    declaredfiled.set(dog2, "汪汪");
    value2 = declaredfiled.get(dog2);
    System.out.println(value2);
  }

运行如下
在这里插入图片描述
其中看到使用getField(String name)的方法,正确获取到了null和设置了“二哈”

至于下面的报错,是因为对于私有成员变量默认是没有权限获取和设置的,需要添加如下权限才可以成功进行

    Dog dog2 = new Dog();
    Object value2 = declaredfiled.get(dog2);
    System.out.println(value2);
    
    declaredfiled.setAccessible(true);//非常关键
    
    declaredfiled.set(dog2, "汪汪");
    value2 = declaredfiled.get(dog2);
    System.out.println(value2);

3.2.3 小结

  • 设置值
    • void set(Object obj,Object value)
  • 获取值
    • get(Object obj)
  • 忽略访问权限修饰符的安全检查
    • setAccessible(true) 暴力反射,真的是想干啥干啥了

3.3 获取Constructor详解

后面就比较类似了,代码简单说明一下

3.3.1 实例说明

public class RelectDemo3 {

  public static void main(String[] args)
      throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
          InvocationTargetException, InstantiationException {
    Class cls1 = Class.forName("reflect.Dog");
    // 有参构造函数
    Constructor constructor = cls1.getConstructor(String.class, int.class, String.class);
    System.out.println(constructor);
    Object dog = constructor.newInstance("汪汪", 2, "二哈");
    System.out.println(dog);

    // 无参构造函数
    // 方式一,使用Constructor的方法,有点繁琐
    Constructor constructor1 = cls1.getConstructor();
    System.out.println(constructor1);
    Object dog1 = constructor1.newInstance();
    System.out.println(dog1);
    // 方式二,Class对于无参构造函数直接提供了newInstance()
    Object dog2 = cls1.newInstance();
    System.out.println(dog2);
  }
}

执行结果
在这里插入图片描述

3.3.2 小结

  • 创建对象
    • T newInstance(Object… initargs)
    • 如果使用空参数构造方法创建,可以简化成Class对象里的newInstance()方法
    • 如果是私有的构造函数,依旧需要忽略访问权限修饰符的安全检查,setAccessible(true) 暴力反射,真的是想干啥干啥了

3.4 获取Method详解

3.4.1 实例说明

public class RelectDemo4 {

  public static void main(String[] args)
      throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
          IllegalAccessException {
    Class cls1 = Class.forName("reflect.Dog");
    // 执行方法
    System.out.println("---------------执行方法-----------------");
    Method method = cls1.getMethod("play", String.class);
    Dog dog = new Dog();
    method.invoke(dog, "飞盘");

    // 获取所有方法
    System.out.println("---------------获取所有方法-----------------");
    Method[] methods = cls1.getMethods();
    for (Method m : methods) {
      System.out.println(m);
    }
  }
}

执行结果
在这里插入图片描述

3.4.2 小结

  • 执行方法
    • Object invoke(Object obj,Object… args)
    • 如果是私有的方法,依旧需要忽略访问权限修饰符的安全检查,setAccessible(true) 暴力反射,真的是想干啥干啥了

四、java反射用在了哪些地方?

  • 各种各样的框架里,比如Spring框架
  • 各种设计模式里,典型的动态代理模式里
  • 各种各样神奇的功能里,比如IDEA里的.提示
    在这里插入图片描述

五、进阶

如果感兴趣可以继续阅读:
Java反射实战篇<实现一个最最简单的框架>.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值