写点什么

大厂难进?清华学霸整理的 Java 反射机制笔记,让你面试一面即中

发布于: 3 小时前
大厂难进?清华学霸整理的Java反射机制笔记,让你面试一面即中

今日分享开始啦,请大家多多指教~

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 的方法对象

今日份分享已结束,请大家多多包涵和指点!

用户头像

还未添加个人签名 2021.04.20 加入

Java工具与相关资料获取等WX: gsh950924(备注来源)

评论

发布
暂无评论
大厂难进?清华学霸整理的Java反射机制笔记,让你面试一面即中