Java笔记
反射
reflection
引出反射
我们需要怎样才可以读出配置文件的内容,并调用配置文件的方法
配置文件
1 | classPath = com.reflection.Cat |
目标类文件
1 | package com.reflection; |
问题实现
第一点很容易,我们通过创建properties在进行加载,就可以很容易的得到配置中的内容
1 | public class ReflectionQuestion { |
但是我们要怎样取创建配置文件中的对象,和调用对应的方法呢?
我们暂时还没有在不通过在不修改源码的情况下控制程序的办法,在这里我们就需要使用到反射机制来解决,以下是一个快速入门
快速入门
我们需要以下的几个步骤
1.加载类,返回Class类型的对象
1 | //加载类运用一个Class的对象接收类的路径 |
2.通过类得到你加载的类Cat对象实例
1 | //通过Class对象的实例调用newInstance获取到对应类路径下对象的实例 |
3.通过类对象调用getMethod方法得到对应参数名称的方法,存在Method对象中(将方法视为一个对象)
1 | //通过cls类调用方法getMethod获取方法 |
4.最后通过方法对象调用invoke传入对象,完成调用
1 | //最后通过方法对象调用invoke传入对象,完成调用 |
完整代码
可以实现不修改源码的基础上修改程序的内部逻辑
1 | public class ReflectionQuestion { |
输出结果
1 | class com.reflection.Cat |
修改测试
通过修改配置文件,创建一个狗类,调用其中的say方法
配置文件
1 | classPath = com.reflection.Dog |
狗类
1 | package com.reflection; |
运行结果
1 | 大狗大狗叫叫叫~~ |
反射机制原理
原理
1.反射机制允许程序在程序执行期间借助Reflection API获取任何类的内部信息(包括成员变量,构造器,成员方法),并能够操作对象的属性以及方法(反射在设计模式和底层框架都会用的到
2.加载完类后,堆中产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构。而这个对象就像是一面镜子,透过这个镜子可以看到类的结构(所以称之为反射)
反射机制原理图
反射(Reflection)的核心来源是 Java 的 Class 对象,这个对象是程序在 Class 类阶段(加载阶段) 通过 ClassLoader
类加载器,加载后创建的。
在底层编译时
类加载器(ClassLoader
)会将字节码文件加载到 JVM 中,并转换为 Class 类对象,其中包含:
- 字段信息(Field[] fields)
- 构造器信息(Constructor[] cons)
- 方法信息(Method[] ms)
这些信息是 Java 反射的基础,通过 Class
对象,Java 代码可以动态地获取类的详细结构,并在运行时操作类的成员(方法、变量等)。
对用户而言
Java 允许开发者使用反射机制操作 Class 对象
反射的方式:
- 通过
Class
对象获取类的信息(字段、方法、构造器)。 - 通过
Field
访问类的字段。 - 通过
Method
调用类的方法。 - 通过
Constructor
创建对象实例。
反射的优缺点
1.优点:可以动态的创建和使用对象(也是框架的底层核心),使用灵活,没有反射机制,框架就会失去底层支持
2.使用反射基本是解释执行,对执行速度有影响(和传统方式时间差距很大)
有一个setAccessible方法可以操作访问安全检查的开关以此来提升反射的效率
反射相关的类
Class
代表一个类,Class对象表示在某个类加载后在堆中的对象
1 | Class cls = Class.forName(classPath); |
1.Class也是类,继承于Object类
2.Class类对象不是new实例化出来的,而是系统创建的
3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
4.每个类的实例都会记得自己是由哪一个Class实例所生成的
5.通过Class对象可以完整的得到一个类的完整结构(API调用)
6.Class对象是存放在堆中的
7.类的字节码二进制数据,是放在方法区的,称之为类的元数据(包括方法代码,变量名,方法名,访问权限等)
Method
代表类的方法,Method对象表示某个类的方法
1 | Method method01 = cls.getMethod(method); |
Field
代表类的成员变量,Field对象表示某个类的成员变量
1 | Field nameField = cls.getField("name"); |
Constructor
代表类的构造方法,Constructor对象表示某个类的构造器
1 | Constructor constructor1 = cls.getConstructor(); |
当然也可以调用构造器对象构造类的对象实例
Class常用方法(入门)
forName
获取类的对象
1 | Class<?> cls = Class.forName("com.reflection.Cat"); |
1 | Class cls = Class.forName("com.reflection.Cat"); |
前一个输出是哪一个类的Class对象,后一个输出类对象
getPackage
获取类的包名
1 | System.out.println(cls.getPackage()); |
getName
获取类的全路径
1 | System.out.println(cls.getName()); |
newInstance
通过类实例创建对应类的实例
1 | Object obj = cls.newInstance(); |
getField
获取类的属性(无法获取私有属性)
1 | Field field = cls.getField("name"); |
获取所有属性
1 | Field[] fields = cls.getFields(); |
set
设置类属性的值
1 | Object obj = cls.newInstance(); |
获取Class对象方式
在编程的时候一共可以有六种方式可供程序员使用获取Class对象
代码阶段
forName
使用前提:已知一个类的全类名,且类在类的路径下,可以通过Class的静态方法forName获取(可能会抛出ClassNotFound异常)
应用场景:多用于配置文件,获取类全路径,加载类
1 | Class<?> cls = Class.forName("com.reflection.Cat"); |
加载阶段
类.class
从类加载器中获取
使用前提:已知具体的类,通过类的class方法获取,该方式是最为可靠的,程序性能最高
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
1 | Class cls = Cat.class; |
运行阶段
对象.getClass
使用前提:某一个类的实例已经存在,通过该实例的getClass方法获取Class对象
应用场景:通过创建好的对象获取Class对象
1 | Cat cat = new Cat(); |
通过类加载器
调用类加载器的loadClass方法获取
1 | ClassLoader classLoader = cat.getClass().getClassLoader(); |
基本数据类型类对象
数据类型.class
1 | Class<Integer> cls = int.class; |
包装型类对象
调用包装类的静态属性TYPE获取
1 | Class<?> cls = Integer.TYPE; |
类加载
反射机制是Java实现动态语言的关键,也就是通过反射实现动态加载
动态加载与静态加载
静态加载
编译时加载相关的类,如果没有则报错,依赖性较高
动态加载
运行时加载需要的类,如果没有用到缺失的类,就不会报错,降低了依赖性
代码示例
以下是一个动态加载的代码示例:
例如我有一个Cat类,一个House类,但是实际上只有Cat类是在代码中声明,我们利用静态加载一个Cat 和一个House类,代码不通过编译
1 | Cat cat = new Cat(); |
但是我们可以通过类加载实现动态加载,只有在运行的时候没有获取到对应的类时候才会发出运行时错误
1 | public class Class01 { |
在输入2的时候报错
1 | java.lang.ClassNotFoundException: com.reflection.House |
类加载的流程图
加载阶段(由JVM控制)
JVM机将字节码从不同的数据源(class文件、jar包,网络)转化为二进制字节流加载到内存中,并且生成代表该类的java.lang.Class对象
连接阶段(由JVM控制)
验证
确保Class字节流中的信息符合当前虚拟机的要求,并且不会危害虚拟机的安全(这个阶段是可以手动关闭的)
在编译运行的时候:使用-Xverify:none参数关闭大部分的类验证措施,以缩短虚拟机类加载的时间
准备
JVM在该阶段对静态变量,分配内存,并默认初始化(0,null,false等),这些变量所使用的内存都在方法区中进行分配
以一个类来举例
1 | class A { |
解析阶段
虚拟机将常量池中的符号引用替换为直接引用的过程
初始化(由程序员控制)
1.到初始化阶段才开始执行类中定义的Java程序代码,此阶段执行的是
2.
3.虚拟机会保证一个类的
正是通过3的线程加锁,才保证了某一个类的对象在堆中只存在一个
在代码编写的时候:有一个setAccessible(爆破)方法可以操作访问安全检查的开关以此来提升反射的效率
获取类信息
相关方法
现有一个类Person
1 |
|
Class类
getName
获取全类名
1 | Class cls = Class.forName("com.reflection.Person"); |
getSimpleName
获取简单类名
1 | Class cls = Class.forName("com.reflection.Person"); |
getFields
获取所有public修饰的属性,包含本类以及父类
1 | Field[] fields = cls.getFields(); |
1 | public int com.reflection.Person.pub |
getDeclaredFields
获取本类中所有属性
1 | Field[] fields = cls.getDeclaredFields(); |
1 | public int com.reflection.Person.pub |
getMethods
获取本类中由public修饰的所有方法,包含本类以及父类
1 | Class cls = Class.forName("com.reflection.Person"); |
1 | say |
getDeclaredMethods
获取本类中所有方法
1 | Class cls = Class.forName("com.reflection.Person"); |
1 | void com.reflection.Person.hi() |
getConstructors
获取public修饰的构造器,包含本类
1 | Class cls = Class.forName("com.reflection.Person"); |
1 | public com.reflection.Person() |
getDeclaredConstructors
获取本类所有的构造器
1 | public com.reflection.Person() |
getPackage
以Package的形式返回包信息
1 | Class cls = Class.forName("com.reflection.Person"); |
getSuperclass
以Class的形式返回父类的信息
1 | public static void main(String[] args) throws Exception { |
getInterfaces
以Class的形式返回接口信息
1 | public class Class01 { |
getAnnotations
以Annotation数组的形式返回注解信息
1 | public static void main(String[] args) throws Exception { |
Filed类
getModifiers
以int形式返回修饰符
默认修饰符0 ,public 1 , private 2 , protected 4 ,static 8 ,final 16 public+static 9(1 + 8)
1 | Field field = cls.getField("pub"); |
getType
以Class形式返回类型
1 | Class<?> cls = Class.forName("com.reflection.Person"); |
getName
返回属性名
1 | Class<?> cls = Class.forName("com.reflection.Person"); |
Method类
getModifiers
以int形式返回修饰符
1 | Class<?> cls = Class.forName("com.reflection.Person"); |
getReturnType
以Class形式返回类型
1 | Class<?> cls = Class.forName("com.reflection.Person"); |
getName
返回方法名
1 | Class<?> cls = Class.forName("com.reflection.Person"); |
getParameterTypes
以Class数组返回参数类型数组
1 | Class<?> cls = Class.forName("com.reflection.Person"); |
Constructor类
和Method类基本一致,没有getReturnType方法(没有返回值)
反射爆破
反射爆破创建实例
有以下几个构造器
1 | public Person() {} |
大体分为两种方式
方式一:调用类中pulic修饰的无参构造器
方式二:调用类中的指定构造器
Constructor类相关方法
setAccessible
是Java程序设计者留的后门,可以破解私有构造器的限制
爆破(暴力破解)
1 | constructor.setAccessible(true); |
newInstance
调用构造器
1 | Object obj = constructor.newInstance(10);//通过构造器创建对象 |
Class类相关方法
newInstance
调用类中的无参构造器,获取对应类的对象
1 | Object obj = cls.newInstance(); |
getConstructor
根据参数列表,获取对应的构造器对象(获取public构造器对象)
1 | Constructor<?> constructor = cls.getConstructor(int.class);//获取到公有的构造器 |
getDecalaredConstructor
根据参数列表,获取对应的构造器对象(获取本类所有的构造器对象)
用普通的方式无法通过private修饰的构造器创建对象实例,但是通过反射可以实现
但是后续调用私有构造器需要爆破(暴力破解private)(单独使用newInstance会发生运行错误)
1 | Constructor<?> constructor = cls.getDeclaredConstructor(int.class,int.class);//获取到所有的构造器 |
通过爆破使用私有构造器
1 | Constructor<?> constructor = cls.getDeclaredConstructor(int.class,int.class);//获取到所有的构造器 |
反射爆破操作属性
访问属性的相关方法
获取Field对象
Field f = class对象.getDeclaredField(属性名称)
1 | Field field = cls.getDeclaredField("pri"); |
爆破
1 | field.setAccessible(true); |
访问
设置属性值
f.set(obj, 值)
1 | field.set(obj, 100); |
获取属性值
f.get(obj)
1 | field.get(obj) |
如果是静态属性,则set和get中的obj参数可以写为null
private修饰的属性值
1 | public class Class01 { |
反射爆破操作方法
访问方法相关方法
getDeclaredMethod
获取类所有的方法
1 | Method method = cls.getDeclaredMethod("hi"); |
setAccessible
设置方法爆破
1 | method.setAccessible(true); |
invoke
传入class类对象实例调用方法
如果是静态方法传入null即可
invoke方法参数后是一个可变参数,可供传入参数
1 | Object returnValue = method.invoke(obj);//静态方法hi |
在反射中如果有返回值统一返回Object类型的值
通过反射调用private方法
1 | public class Class01 { |
课后习题
例题一
1.定义PrivateTest类,由私有属性name,属性值为hellokitty
2.提供getName公共方法
3.创建PrivateTest类,利用Class类得到私有属性name属性,修改name属性值
4.使用getName方法打印name属性值
题解
写出PrivateTest类
1 | package com.reflection; |
主方法调用
1 | public class Test01 { |
例题二
1.利用Class类的forName方法得到File类的class对象
2.在控制台打印出File类的所有构造器
3.通过newInstance的方法创建File对象,并创建 E:\mynew.txt 文件
1 | public class FileTest{ |