JavaBasic-10-反射
反射机制
基本介绍
- 反射机制允许程序在执行期借助⼦Reflection API 取得任何类的内部信息(比如成员变量,构造器,成员⽅法等),并能操作对象的属性及⽅法
- 加载完类之后,在堆中就产⽣了⼀个Class类型的对象(⼀个类只有⼀个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像⼀⾯镜⼦,透过这个镜⼦看到类的结构,所以,形象的称之为:反射
反射相关类
类 | 说明 |
---|---|
java.lang.Class | 代表⼀个类,Class对象表示某个类加载后在堆中的对象 |
java.lang.reflect.Method | 代表该类的⽅法 |
java.lang.reflect.Field | 代表类的成员变量 |
java.lang.reflect.Constructor | 代表类的构造⽅法 |
案例
读写配置文件
1 | Properties properties = new Properties(); |
使⽤反射机制解决
1 | // 加载类, 返回Class类型的对象cls |
Class类
基本介绍
- Class 也是类,因此也继承Object类
- Class 类对象不是 new 出来的,⽽是系统创建的类的 Class 类对象,在内存中只有⼀份,且只加载⼀次
- 每个类的实例都会记录它由哪个 Class 实例⽣成
- 通过 Class 的⼀系列 API 可以完整地得到⼀个类的完整结构
- Class 对象是存放在堆的
- 类的字节码⼆进制数据,是放在方法区的,有的地⽅称为类的元数据(包括⽅法代码,变量名,⽅法名, 访问权限等)
Class 类对象获取 1
前提:已知⼀个类的全类名,且该类在类路径下,可通过 Class 类的静态⽅法 forName() 获取
1 | Class cls1 = Class.forName("java.lang.Cat") |
应⽤场景:多⽤于 配置⽂件,读取类全路径,加载类
Class 类对象获取 2
前提:若已知具体的类,通过类的 class 获取,该⽅式最为安全可靠,程序性能最⾼
1 | Class cls2 = Cat.class |
应⽤场景:多⽤于 参数传递 ,⽐如通过反射得到对应构造器对象
Class 类对象获取 3
前提:已知某个类的实例,调⽤该实例的 getClass() ⽅法获取 Class 对象
1 | Class class = 对象.getClass(); |
应⽤场景:通过创建好的对象,获取 Class 对象
注意:这三种方式获取的 Class 对象是同一个 Class 对象,一个类在 JVM 中只会有一个 Class 对象
类加载器
1 | ClassLoader cl = 对象.getClass().getClassLoader(); |
基本数据(int,char,boolean,float,double,byte,long,short)
1 | Class cls = 基本数据类型.class |
基本数据类型对应的包装类,可以通过 .type 得到 Class 类对象
1 | Class cls = 包装类.TYPE |
有 Class 对象的类型
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接⼝
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
常用方法
方法 | 说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型等)名称 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors0 | 返回一个包含某些Constructor对象的数组 |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod(Striing name,Class paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
类加载
基本介绍
类加载器负责将 .class 文件(存储的物理文件)加载到内存中
反射机制是 java 成为动态语⾔的关键,也就是通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运⾏时加载需要的类,如果运⾏时不⽤该类,即使不存在该类,则不报错,降低了依赖性
类加载时机
- 创建类的实例
- 调用类的类方法
- 访问类或接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象
- 初始化某个类的子类
- 直接使用 java.exe 命令来运行某个主类
创建⼀个对象时,在⼀个类的调⽤顺序:
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调⽤的优先级一样,如果有多个静态代码块和多个静 态变量初始化,则按他们定义的顺序调用)
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调⽤的优先级⼀样,若果有多 个普通代码块和多个普通属性初始化,则按定义顺序调⽤)
- 调用构造⽅法
创建子类时的顺序
- 父类的静态代码块和静态属性(优先级⼀样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级⼀样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级⼀样,按定义顺序执行)
- 父类构造⽅法
- 子类的普通代码块和普通属性初始化(优先级⼀样,按定义顺序执行)
- 子类构造⽅法
类加载过程
其中验证、准备、解析这三步属于 链接 的过程
加载
通过类的全限定名来获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为运行时数据结构(加载到内存中)
在内存中生成一个代表这个类的 java.lang.Class 对象(任何类被使用时,系统都会为它建立一个 java.lang.Class 对象)
链接
- 验证:确保 Class 文件字节流中包含的信息符合虚拟机要求,不会危害虚拟机自身安全
- 准备:为类的 类变量 分配内存,并设置 默认 初始化值
- 解析:将二进制数据流中的 符号引用 替换为 直接引用 ,意思是找到需要用到的类
初始化:根据程序员通过程序制定的主观计划去初始化 类变量 和其他资源(静态变量赋值和初始化其他资源)
类加载器的分类
- 启动类加载器(Bootstrap ClassLoader):虚拟机内置的类加载器
- 平台类加载器(Platform ClassLoader):负责加载JDK中一些特殊的模块
- 系统类加载器(System ClassLoader):负责加载用户类路径上所指定的类库
双亲委派模型
- 类加载器收到加载任务时会将加载任务逐层向上传递给父类加载器,最终到达顶层的启动类加载器
- 当启动类加载器不能完成加载任务时,再委托给下层的子类加载器,最终完成类的加载
- ClassLoader 对象的 getParent() 方法可以获取父类加载器
常用方法
方法名 | 说明 |
---|---|
public static ClassLoader getSystemClassLoader() | 获取系统类加载器 |
public InputStream getResourceAsStream(String name) | 加载某一个资源文件 |
反射获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组(包括私有) |
Constructor<?> getConstructor(Class<?>…parameterTypes) | 获取单个、公共的构造方法对象,如果是无参构造则该方法参数留空,如果是有参则该方法参数是参数类型的Class对象 |
Constructor<?> getDeclaredConstructor(Class<?>…parameterTypes) | 获取单个、公共或私有的构造方法对象,如果是无参构造则该方法参数留空,如果是有参则该方法参数是参数类型的Class对象 |
由获取的构造方法对象创建对象
可以利用构造方法类 Constructor 中的
T newInstance(Object...initargs)
方法来创建对象如果该构造方法是 私有 的构造方法,则创建对象之前需要使用Constructor类中的
void setAccessible(boolean b)
方法临时取消访问检查
反射获取Class对象
1 | class Student{ |
反射获取公共的构造方法
1 | Constructor publicConstructor = clazz.getConstructor(String.class, int.class); |
反射获取私有的构造方法
1 | Constructor privateConstructor = clazz.getDeclaredConstructor(String.class); |
反射获取成员变量
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 根据变量名获取一个公共成员变量对象 |
Field getDeclaredField(String name) | 根据变量名获取一个成员变量对象 |
由 Field 对象操作成员变量
void set(Object o,Object v)
,设置某个成员变量的值,第一个参数是需要设置成员变量的对象,第二个参数是成员变量的值Object get(Object o)
,获取某个对象的当前成员变量的值- 注意,如果成员变量是私有的,在使用 get 和 set 方法时也需要 使用setAccessible()方法暂时取消访问检查
1 | // 1.获取类对象 |
反射获取成员方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 获取所有公共成员方法对象数组 |
Method[] getDeclaredMethods() | 获取所有成员方法对象数组 |
Method getMethod(String name,Class<?>…parameterTypes) | 获取一个公共的成员方法对象,第一个参数是方法名,第二个参数是方法的参数的Class对象 |
Method getDeclaredMethod(String name,Class<?>…parameterTypes) | 获取一个成员方法对象,第一个参数是方法名,第二个参数是方法的参数的Class对象 |
由 Method 对象操作成员方法
- 使用
Object invoke(Object o,Object...args)
方法可以运行成员方法 - 第一个参数是指调用该方法的对象
- 第二个参数是指该方法的参数
- 返回值 Object 是成员方法的返回值,如果没有则可以不用接收
关闭访问检查:
- Method 和 Field、Constructor 对象都有 setAccessible() ⽅法
- setAccessible 作⽤是启动和禁⽤访问安全检查的开关
- 参数值为 true 表示 反射的对象在使⽤时取消访问检查,提⾼反射的效率。
- 参数值为 false 则表示反射的对象执⾏访问检查
泛型擦除
反射是作用在 运行时 的技术,而泛型 只在编译阶段约束只能操作某种数据类型,使用反射的话泛型将不能产生对我们产生约束,此时就相当于泛型被擦除了。
编写如下代码时,往集合添加 Integer 类型数据不会产生报错,而添加其他类型数据将无法通过编译。
1 | ArrayList<Integer> list = new ArrayList<>(); |
而使用反射操作,此时集合的泛型将不能产生约束,可以为集合存入其他任意类型的元素的。泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成 Class 文件进入运行阶段时其真实类型都是 ArrayList。
1 | // 1.创建对象 |
可以看到外面不单单存在 Integer 类型数据,还存在它所不允许的 String 类型数据,跳过了泛型对我们的约束作用。
通用框架的底层原理
给你任意一个对象,在不清楚对象字段的情况,把对象的字段名和对应值存储到文件
- 接受任意对象
- 使用反射获取对象的Class类对象,获取全部成员变量信息
- 遍历成员变量得到它们在对象中具体值,然后存入文件