今日分享开始啦,请大家多多指教~
1.一个需求引出反射
需求如下:
根据配置文件re.properties中的指定信息,创建Cat对象并调用方法hi
配置文件中代码:classfullpath=com.hspedu.Cat,method=hi
这样的需求在学习框架时非常多,通过外部文件配置,在不修改源码情况下控制程序
符合设计模式的ocp原则(开闭原则:不修改源码,扩容功能)
代码如下:
1.使用Properties类读写配置文件
2.使用传统方式创建对象行不通,需要使用 反射机制
- 加载类,返回Class类型的对象
- 通过 cls对象 得到加载的类 com.hspedu.Cat 的对象
- 通过 cls对象 得到加载的类 com.hspedu.Cat 的 methodName 方法对象,在反射机制中,可以把方法视为对象(万物皆对象)
- 通过 method1 调用方法:即通过方法对象实现调用方法
此时我们发现,我们只需要将re.properties中的 method=hi 改成 method=cry,就会调用cry(),不需要修改源码,反射机制非常强大!
2、反射机制
2.1 Java Reflection
反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(比如:成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架的底层都会用到。
加载完类之后,在堆内存中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以形象称之为:反射。
2.2 反射机制原理图
2.3 反射机制可以完成的功能
- 在运行时,判断任意一个对象所属的类
- 在运行时,创建任意一个类的对象
- 在运行时,得到任意一个类所具有的成员变量和方法
- 在运行时,调用任意一个对象的成员变量和方法
- 生成动态代理
2.4 反射相关的主要类
这些类在 java.lang.reflection包下:
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆内存中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
代码展示如下:
2.5 反射优点和缺点
优点:可以动态地创建和使用对象(就是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
缺点:使用反射基本是解释执行,对执行速度有影响
2.6 反射调用优化——关闭访问检查
- Method、Field、Constructor对象都有setAccessible()方法
- setAccessible()作用是启动和禁用访问安全检查开关
- 参数为true表示:反射的对象在使用时取消访问检查,提高反射的效率。
- 参数为false表示:反射的对象执行访问检查
代码展示如下:
3、Class类
3.1 基本介绍
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个Class实例生成
- 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
- Class对象存放在堆内存中
- 类的字节码二进制数据存放在方法区中,有的地方称为类的元数据(包括:方法,变量名,方法名,访问权限等)
3.2 Class类的常用方法
代码展示如下:
4.获取Class类对象
5.哪些类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- 接口
- 数组
- 枚举
- 注解
- 基本数据类型
- void
代码展示如下:
6.类加载
6.1 基本说明
反射机制是Java实现动态语言的关键,也就是通过反射实现类的动态加载。
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性
6.2 类加载时机
- 当创建对象时(new)——静态加载
- 当子类被加载时,父类也加载——静态加载
- 调用类中的静态成员时——静态加载
- 通过反射——动态加载
6.3 类加载过程图
6.4 类加载三个阶段完成任务
6.4.1 加载阶段
6.4.2 连接阶段——验证
1.目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2.包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证[举例说明]
3.可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
6.4.3 连接阶段——准备
1. JVM 会在该阶段对静态变量, 分配内存并默认初始化(对应数据类型的默认初始值,如0、OL、null. false 等)。这些变量所使用的内存都将在方法区中进行分配。
2.举例说明: ClassLoad02.java
6.4.4 连接阶段——解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
6.4.5 Initialization(初始化)
1.到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行。
< clinit>()方法的过程。
2. <clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,井进行合井。[举例说明ClassLoad03.java]
3.虚拟机会保证一个类的 <clinit> ()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类, 那么只会有一一个线程去执行这个类的<clinit> 0方法,其他线程都需要阻塞等待,直到活动线程执行<clinit> 0方法完毕[debug源码]
代码展示如下:
1.加载B类,并生成对应的Class类对象
2.连接 num = 0;
3.初始化阶段:依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并。
7、通过反射获取类的结构信息
7.1 java.lang.Class类
com.hspedu.reflection ReflectionUtilsjava
1. getName:获取全类名
2. getSimpleName:获取简单类名
3. getFields:获取所有public修饰的属性,包含本类以及父类的
4. getDeclaredFields:获取本类中所有属性
5. getMethods:获取所有public修饰的方法,包含本类以及父类的
6. getDeclaredMethods:获取本类中所有方法
7. getConstructors:获取本类所有public修饰的构造器
8. getDeclaredConstructors:获取本类中所有构造器
9. getPackage:以Package形式返回包信息
10.getSuperClass:以Class形式返回父类信息
1.getinterfacs:以Class[]形式返回接口信息
12.getAnnotations:以Annotation[]形式返回注解信息
7.2 java.lang.reflect.Field类
1. getModifiers:以int形式返回修饰符
[说明:默认修饰符是0,public 是1,private 是2,protected 是4 ,static是8,final 是16] , public(1) + static (8) = 9
2. getType:以Class形式返回类型
3. getName:返回属性名
7.3 java.lang.reflect.Method类
1. getModifiers:以int形式返回修饰符
[说明:默认修饰符是0,public 是1,private是2,protected是4,static是8,final 是16]
2. getReturnType:以Class形式获取返回类型
3. getName:返回方法名
4. getParameterTypes:以Class[]返回参数类型数组
7.4 java.lang.reflect.Constructor类
1. getModifiers:以int形式返回修饰符
2. getName:返回构造器名 (全类名)
3. getParameterTypes:以Class[返回参数类型数组
代码展示如下:
8、通过反射创建对象
1.方式一: 调用类中的public修饰的无参构造器
2.方式二:调用类中的指定构造器
3. Class类相关方法
- newlnstance :调用类中的无参构造器,获取对应类的对象
- getConstructcor(Class..clazz):根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor(Class..clazz):根据参数列表,获取对应的所有构造器对象
4. Constructor类相关方法
- setAccessible:暴破
- newlnstance(object...obj):调用构造器
测试 1:通过反射创建某类的对象,要求该类中必须有 public 的无参构造
测试 2:通过调用某个特定构造器的方式,实现创建某类的对象
1. 先获取到User类的Class对象
2. 通过public的无参构造器创建实例
3. 通过public的有参构造器创建实例
- 先得到对应构造器
- 创建实例,并传入实参
4. 通过非public的有参构造器创建实例
- 得到private的构造器对象
- 创建实例
9、通过反射访问类中的成员
9.1 访问属性
1.根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名);
2.暴破: f.setAccessible(true); //f是Field
3.访问
- f.set(o,值); //o表示对象
- syso(f.get))://o表示对象
4.注意:如果是静态属性,则set和get中的参数o,可以写成null
代码展示如下:
1. 得到Student类对应的 Class对象
2. 创建对象
3. 使用反射得到age 属性对象
4. 使用反射操作name 属性
9.2 访问方法
1.根据方法名和参数列表获取Method方法对象: Method m =
clazz.getDeclaredMethod(方法名,XX.class); //得到本类的所有方法
2.获取对象: Object o= clazz.newInstance();
3.暴破: m.setAccessible(true);
4.访问: Object returnValue = m.invoke(o,实参列表);//o就是对象
5.注意:如果是静态方法,则invoke的参数o,可以写成null!
代码展示如下:
1. 得到Boss类对应的Class对象
2. 创建对象
3. 调用public的hi方法
- 得到hi方法对象
- 调用
4. 调用private static 方法
- 得到 say 方法对象
- 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
- 因为say方法是static的,还可以这样调用 ,可以传入null
5. 在反射中,如果方法有返回值,统一返回Object , 但是它运行类型和方法定义的返回类型一致。
10、练习案例
练习一
通过反射修改私有成员变量com.hspedu.homework Homework01.java
1.定义PrivateTest类,有私有name属性,并且属性值为hellokitty
2.提供getName的公有方法
3.创建Private Test的类, 利用Class类得到私有的name属性,修改私有的name属性值,并调用getName()的方法打印name属性值
代码展示如下:
定义PrivateTest类,有私有name属性,并且属性值为hellokitty
提供getName的公有方法
创建PrivateTest的类,利用Class类得到私有的name属性,修改私有的name属性值,并调用getName()的方法打印name属性值
1. 得到 PrivateTest类对应的Class对象
2. 创建对象实例
3. 得到name属性对象
4. 暴破name
5. 得到getName方法对象
6. 因为getName() 是public,所有直接调用
练习二
利用反射和File完成以下功能Homework02.java
1.利用Class类的forName方法得到File类的class 对象
2.在控制台打印File类的所有构造器
3.通过newInstance的方法创建File对象, 并创建E:\mynew.txt文件
提示创建文件的正常写法如下:
File file = new File(" d:\aa.txt");//内存
file.createNewFile();//方法,才能真正地创建文件
代码展示如下:
利用Class类的forName方法得到File类的class 对象
在控制台打印File类的所有构造器
通过newInstance的方法创建File对象,并创建D:\mynew.txt文件
1. Class类的forName方法得到File类的class 对象
2. 得到所有的构造器
3. 指定的得到 public java.io.File(java.lang.String)
4. 得到createNewFile 的方法对象
今日份分享已结束,请大家多多包涵和指点!