Java学习笔记——反射(一文读懂什么是反射)

反射机制

什么是反射,为什么要使用反射?

首先先看两个定义

  • 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码

  • 反射:将类的各个组成部分封装为其他对象,这就是反射机制

反射的一个典型应用就是框架设计,在软件开发中,我们希望软件的设计能有更大的适用性。其思想某种程度上与泛型相似,泛型中的思想是:我们不希望某个方法只适用于某个特定类,而只是希望定义一个规范,使得该方法适用于所有会调用该方法的类。而反射的思想是:我们不希望软件的运行依赖某些特定类,而是在软件运行过程中动态地加载所需类最大程度上提高程序的可扩展性

⭐️ 反射机制有什么用?

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象(服务器跑起来了不能再重新编译)
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理
  1. 说明:如果想要完成对对象的操作,最重要的就是获取.Class类对象,java中也提供了一些获取Class类对象的API接口

如何使用反射?

在了解反射机制前,首先需要知道的的是java中一个从写到产生一个实例对象之间经历了什么,过程见如下流程图

image-20210126101951768

阶段一:源代码阶段——我们写的.java代码经过javac编译后会生成.class文件,这一阶段统称为源代码阶段。

阶段二:Class类对象阶段——在该阶段,java中的类加载器会将第一阶段得到的.class文件加入进入JVM中,同时解析初其被定义了哪些成员方法,成员变量,构造方法等类的基本属性,将其存储在内存中,以供使用。在上图中,执行完第二阶段后,系统就可以识别Person对象,但我们输入new Person()时,系统就不会判断代码为错误。

阶段三:Runtime运行时——当我们在代码中使用new时,就是将存储在内存中的Class类实例化的过程。例如上图中我们就通过new Person()方法得到一个实例化person对象。

获取目标对象类

上文提到,反射最重要的就是动态的获取目标类对象。因此可以预料得到,在上述的三个阶段中,反射都提供了对应的方法来获取类对象,以Person类为例

  1. 在Source源代码阶段,(此时内存中没有Person类)

    Class.forName("全类名");:将字节码文件加载进内存,返回class对象(相当于自行把该类加载进入内存,并返回该类

    ⭐️ 由于这里传入的是字符串,这种方法多用于配置文件。将类名定义在配置文件中,读取文件加载类,该方法最能体现动态性

  2. 在Class类对象阶段(此时内存中已经有Person类

    类名.class:通过类名的属性.class属性来获取

    • 多用于参数的传递
  3. 在运行阶段(此时已经有了Person的对象实例

    对象.getClass():getClass()方法在Object类中定义的,java为每一个object类都提供了该方法。

    • 多用于对象获取字节码的方式。

代码实例如下:

public static void main(String[] args) {
	//1.Class.forName("全类名")
  Class cls1 = Class.forName("cn.itcast.domain.Person");	//Class类静态方法
  System.out.println(cls1);
  //2.类名.class
  Class cls2 = Person.class;		//成员属性
  System.out.println(cls2);
  //3.对象.getClass()
  Person p = new Person();
  Class cls3 = p.getClass();		//object的成员方法
  System.out.println(cls3);
  
  // 可以用==来比较三个对象,进而判断其是否是同一个变量(==比较地址,equals比较的是字符类型)
  System.out.println(cls1==cls2);//true
  System.out.println(cls1==cls3);//true
}

⭐️关于Class类的的说明

  • 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个对象**。但不同的实例类,对应的Class类对象都不相同**。
  • 类加载过程:程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class)结尾;接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件(.class)加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例(例如这里的Person类)。
  • Class实例对应着加载到内存中的一个运行时类(不是类的对象)
  • 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

补充说明:Java中的可以获取Class实例不光是我们自定义的类Person类,也可以是如下可以是如下类型

  1. Class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  2. Interface:接口
  3. []:数组
  4. enum:枚举
  5. annotation:枚举
  6. primitive type:基本数据类型
  7. void

构造目标类对象

当获取到目标类后,java提供了相关的API供我们使用来完成希望的功能

构造对象时涉及的相关API

  • java.lang.Class:反射的源头
  • java.lang.reflect.Method:可用于获取目标类的方法
  • java.lang.reflect.Field:用于获取目标类的成员变量
  • java.lang.reflect.Constructor:用于获取目标类的构造器
获取类的构造器并构建对象

获取构造方法们;

  • Constructor<?>[] getConstructors():获取所有public的构造方法**(限public修饰)**
  • Constructor<T> getConstructor(Class<?>... parameterTypes):获取某个指定参数的构造方法**(限public修饰)**
  • Constructor<?>[] getDeclaredConstructors():获取所有构造方法
    • 用带Declared方法去访问private方法时,需要忽略权限修饰符的安全检查——使用暴力反射。
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取某个指定参数的构造方法

利用构造方法们创建实例化对象

  • 使用构造器的newInstance()方法构造对象
// 下面的personClass 是person的Class类对象
// Class personClass = Person.class;

Constructor constructor = new personClass.getConstructor(String.class,int.class);	//获取参数为(String,int)的构造方法
Constructor[] constructors = new personClass.getConstructor();		//获取所有构造方法,可以通过索引选择
  
Object person = constructor.newInstance("张三",23);//用该构造方法创建一个新的对象
  • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
Object o = personClass.newInstance();
获取类的成员变量

获取成员变量们;

  • Field[] getFields():获取所有public修饰的成员变量
  • Field getField(String name):获取指定名字的成员变量
  • Field[] getDeclaredFields():获取所有的成员变量
    • 用带Declared方法去访问private方法时,需要忽略权限修饰符的安全检查——使用暴力反射。
  • Field getDeclaredField(String name):获取所指定名字的成员变量

代码实例如下:

Field name = personClass.getDeclaredField("name"); //获取Person类的名为name的变量

获取的变量可以通过对象.set(对象,新属性)赋值

name.set(person,"zhangsan");// 设置person对象的name属性值为zhangsan
获取并执行类的成员方法

获取成员方法们;

  • Method[] getMethods():获得所有public修饰的成员方法
  • Method getMethod(String name,Class<?>... parameterTypes):获得指定参数内容的public修饰的成员方法
  • Method[] getDeclaredMethods():获得所有成员方法
    • 用带Declared方法去访问private方法时,需要忽略权限修饰符的安全检查——使用暴力反射。
  • Method getDeclaredMethod(String name,Class<?>... parameterTypes):获得指定参数内容的成员方法
Method eat_method = personClass.getMethod("eat");	//获取Person类的名为  eat 的成员方法
eat_method.invoke(person);		//让person对象执行 eat 方法


eat_method2.personClass.getMethod("eat",String.class);	// 获取Person类的 名为eat 参数为 String 的成员方法
eat_method2.invoke(p,"饭");	// 让person对象执行该成员方法,并设置参数为"饭"


Methods[] methods = personClass.getMethods();  // 获取Person类所有public修饰的方法
//	注意这里会打印出所有方法,包括其父类的方法
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值