文章目录
一、简单说明 Java 中反射的基本概念
Java 的反射机制是指,在运行状态中:
对于任何一个 类 都能知道这个类中的所有属性和方法。
对于任意一个对象,都能 调用 其中的任意一个属性和方法。
这种 动态的获取信息 和 动态的调用对象方法 的功能被称之为 Java 的反射。
简而言之,通过反射,可以在运行时获取类的信息,创建类的实例,调用类的方法,访问和修改类的字段等。
二、简单介绍反射中常用的几个类
-
java.lang.Class: 代表类的元数据。每一个类都有与之对应的 Class 对象,可以通过 .class 语法、Class.forName() 方法或对象的 getClass() 方法来获取。
-
java.lang.reflect.Method: 代表类的获取方法。
-
java.lang.reflect.Field: 代表类的字段。
-
java.lang.reflect.Constructor: 代表类的构造函数。
三、通过代码实现常见的反射
这里需要做一些前置的准备工作:
创建出一个被反射的类,这里创建了一个 Student 类:
public class Student {
// ------在这里定义出一些字段------
public String name;
public String name02;
protected int age;
char sex;
private String phoneNUM;
// 这里重写一下 toString 方法
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex
+ ", phoneNum=" + phoneNUM + "]";
}
// ------这里创建出一个 Student 类来便于反射获取并使用------
// 没有属性的带有一个 String 参数的构造方法
Student (String str) {
System.out.println("没有属性的带有 String 参数的构造方法 str = " + str);
}
// 公共类型的构造方法
public Student() {
System.out.println("没有参数的构造方法(public 类型)");
}
// 包含一个参数的构造方法
public Student(char name) {
System.out.println("(包含参数的 public 类型方法)姓名:" + name);
}
// 有多个参数的构造方法
public Student(String name, int age) {
System.out.println("(公有的包含多个参数的构造方法)姓名:" + name + "年龄:" + age);
}
// 收到保护的构造方法
protected Student(boolean n) {
System.out.println("这是收到保护的构造方法 n = " + n);
}
// 私有的构造方法
private Student(int age) {
System.out.println("这是私有的构造方法 年龄:" + age);
}
// 创建 Student 类中的成员方法
public void show1(String s) {
System.out.println("调用了:共有的,包含 String 类型的 show1() 方法 s: " + s);
}
protected void show2() {
System.out.println("调用了:受保护的,无参数的 show2() 方法");
}
void show3() {
System.out.println("调用了:默认的,没有参数的 show3()");
}
private String show4(int age) {
System.out.println("调用了:私有的,带返回值的,int 参数的 show4(): age = " + age);
return "return show04()";
}
// ------通过反射运行配置文件内容------
public void show() {
System.out.println("this is show()");
}
// ------设置一个 main 方法------
public static void main(String[] args) {
System.out.println("Student 中的 main 方法执行了!!");
}
}
这个类中的所有方法都是用来辅助后续所有的代码解释的!!!
1、代码解释如何获取 Class 类对象
首先先来解释一下如何获取 Class 类对象。
这里用不到这个 Student 类中的任何方法,在这里 Student 类的主要作用就是被 Class 类反射调用,获得到当前的类对象。
在这里,有三种方式可以获取到 Student 类对象,下面通过代码分别进行简单的解释:
- 第一种,通过 Object 中的 getClass() 进行获取:
在这种获取方式中,需要先通过 new 来生产出一个 Class 对象,才能被获取。
代码如下:
// 第一种:通过 Object ——> getClass()
Student stu1 = new Student(); // 通过 new 来产生一个 Student 对象,一个 Class 对象
Class stuClass = stu1.getClass(); // 这里则是通过这个对象中的 get 操作来获取到 Class 对象 (当然也要被 Class 类型进行接受)
System.out.println(stuClass.getName()); // 打印当前 Class(类) 的类名称
运行结果如下:
这里的 getClass() 方法获取到的元素对应的返回类型是 Class 类型。
需要注意的是,没有参数的构造方法,在该类被创建的同时也会被创建出来。
- 第二种,通过 类 的“静态” Class 属性获取
这里直接通过 类名.class 的形式即可获取。
代码如下:
// 第二种:通过 类 的 “静态” class 属性来获取
Class ints = Integer.class; // Integer 类型的 “静态” class 属性也是可以被获取的
Class stuClass2 = Student.class;
System.out.println("这里是 Integer : " + ints);
System.out.println("这里是 stuClass2: " + stuClass2); // 可以看到这里获取到的是 包名 + 类名
运行结果如下:
观察结果,不只是自己创建的类,jar 包中自带的类属性同样可以被获取到。
- 第三种,通过 Class 类中的静态方法 forName() 获取
这种获取方式是最常用也是相对最容易理解的一种方式,(在之后的代码中如果需要获取某个类,本人都会使用这种方式获取!!)
try {
Class stuClass3 = Class.forName("Student");
Class Int = Class.forName("java.lang.Integer"); // 这里的 String 类型字符串是要通过包名开始写到详细的类名
System.out.println("这是 Student 类: " + stuClass3);
System.out.println("这是 Integer 类: " + Int);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
运行结果如下:
2、获取 Class 中的构造方法
这里的构造方法,在前面的前置代码中已经实现,如图:
所谓构造方法就是面相对象编程中的一个特殊方法,在 类实例化 时会被自动调用 构造方法不能像其他方法那样被直接调用。它们只能在新对象创建时由Java虚拟机(JVM)自动调用。
还需要注意的一点是,每一个属性的构造方法都只能创建一次。
对于构造方法获取构造方法,这里要知道一个关键字:
Constructor
(1)获取所有的 public 类型的构造方法
// 1.首先需要加载 Class 类对象
// 这里通过最常用的 forName 方法来获取到类对象
Class stu = Class.forName("Student");
// 创建 Construct 类型的数组,用于接受后续获取到的实例化对象
Constructor[] conArray = null;
// 获取所有的 public 类型的构造方法
System.out.println("********所有的公有的构造方法********");
conArray = stu.getConstructors();
for (Constructor c: conArray) {
System.out.println(c);
}
观察运行结果:
(2)获取所有属性的构造方法
System.out.println("********获取多有的构造方法(包括:私有、受保护、默认、公有)********");
conArray = stu.getDeclaredConstructors();
for (Constructor c: conArray) {
System.out.println(c);
}
观察运行结果:
(3)获取单个的没有参数的 public 属性构造方法
// 获取公有的,没有参数的构造方法
System.out.println("********获取公有的,没有参数的构造方法********");
Constructor con = stu.getConstructor(null);
System.out.println(con);
观察运行结果:
(4)尝试获取单个的包含参数的 private 属性构造方法
// 尝试获取私有的包含参数的构造方法
System.out.println("********获取私有的构造方法,并调用********");
Constructor con2 = stu.getDeclaredConstructor(int.class);
System.out.println("私有的构造方法" + con2);
con2.setAccessible(true); // 暴力访问,直接忽略访问修饰符
// 这就相当于单独的 创建并且调用了通过getDeclaredConstructor获取的构造方法
con2.newInstance(100);
观察运行结果:
总的来讲,可以将这里获取构造方法的形式总结为下面的几点:
- 要获取 多个 public 属性的构造方法使用 —— getConstructors()
- 要获取 多个 “各种” 属性的构造方法使用 —— getDeclaredConstructors()
(这两点需要注意的是要使用 Construct 类型的数组来接受)
- 要获取 单个 public 属性的构造方法使用 —— getConstructor(类型.class)
- 要获取 单个 “其他” 属性的构造方法使用 —— getDeclaredConstructor(类型.class)
(这两点获取 单个 构造方法的情况,需要注意的是方法与多个之间的一个细小差别 “s”)
3、获取 Class 中的单个字段
同样的,这些字段也在之前的前置代码中进行了实现,如图:
为了方便后续代码观察结果,这里重写了一下 toString 方法。
对于 获取字段 这里需要知道一个关键字:
Field
(1)获取所有公共类型的字段
// 首先获取 class 对象
Class stuClass = Class.forName("Student");
// 定义出一个 Field 类型的数组,在这里接受从 student 中返回的值
Field[] fieldArray = null;
// 获取所有的公共类型字段
System.out.println("********获取所有的公共字段********");
fieldArray = stuClass.getFields();
for (Field f : fieldArray) {
System.out.println(f);
}
观察运行结果:
(2)获取 “所有” 类型的字段
System.out.println("********获取所有的字段(包括私有、受保护、默认)********");
fieldArray = stuClass.getDeclaredFields();
for (Field f : fieldArray) {
System.out.println(f);
}
观察运行结果:
(3)尝试获取 “公共” 字段并 调用
// 获取公共字段并且尝试进行调用
System.out.println("********获取公共字段并且调用********");
Field f1 = stuClass.getField("name");
Field f2 = stuClass.getField("name02");
// 这里展现已经获取到了两个 public 类型的 参数
System.out.println("这是 name :" + f1);
System.out.println("这是 name02:" + f2);
// 创建出一个 Student 对象 ==> new Student();
Object obj1 = stuClass.getConstructor().newInstance();
// 这里的 set 就是赋值操作即 ==> stu.name = "名称1"
f1.set(obj1, "名称1");
f2.set(obj1, "名称2");
// 进行验证,这里的操作是将 obj 进行强制类型转换,转换为 Student 之后进行验证
Student stu = (Student) obj1;
System.out.println("验证姓名:" + stu.name);
System.out.println("验证姓名:" + stu.name02);
观察运行结果:
在这里简单解释一下上面的代码:
第一部分:通过 getField 获取到公共类型的字段,并且通过 “打印” 出来演示
第二部分:通过 反射 new 出来一个 Student 对象。之后通过 set 进行赋值操作。
(4)尝试获取 “私有” 字段并 调用
// 尝试获取私有字段并且调用
System.out.println("********获取所有的字段(包括私有、受保护、默认)并且调用********");
Field pf1 = stuClass.getDeclaredField("phoneNUM");
Field pf2 = stuClass.getDeclaredField("age");
System.out.println(pf1);
System.out.println(pf2);
// 暴力反射,解除私有设定
pf1.setAccessible(true);
pf2.setAccessible(true);
// 这里的操作类似于 Student stu = new Student()
Object obj2 = stuClass.getConstructor().newInstance();
pf1.set(obj2, "1539999999999");
pf2.set(obj2, 11);
Student stu2 = (Student) obj2;
System.out.println("获取到所有类型字段后的 toString 方法" + stu2.toString());
观察运行结果:
总的来讲,字段的获取和上面解释过的构造方法获取有着异曲同工之意:
同样可以总结为下面几点:
- 要获取 多个 public 属性的参数需要 —— getFields()
- 要获取 多个 “各种” 属性的参数需要 —— getDeclaredFields()
(这两种获取需要注意的是,得到的元素需要通过 Field 类型的数组来进行接受)
- 要获取 单个 public 属性的参数需要 —— getField(“参数名称”)
- 要获取 单个 “其他” 属性的参数需要 —— getDeclaredField(“参数名称”)
(这两种获取需要注意的是,与 “多个” 获取相比方法缺少了 “s”)
至于在这之中对于 参数值 的设置则在 newInstance() 之后,通过 set() 直接插入即可。
4、针对 Class 中的 main 方法通过反射获取调用
这是在 Student 类中的 main 方法
在这里,对于不是构造方法的调用,这里需要认识到一个关键字:Method
// 首先获取到 Student 类
Class stu = Class.forName("Student");
// 获取 main 方法
Method getMain = stu.getMethod("main", String[].class);
System.out.println(getMain);
// 调用 main 方法
getMain.invoke(null, (Object) new String[]{});
观察运行结果:
可以看到,通过 getMethod() 获取到的 main 方法。
虽然 main 方法比较特殊,但是获取方式仍然根据 参数对应的形式进行获取
最后通过 invoke() 方法调用 main 方法,在这里调用的形式就比较特殊了,因为参数类型是一个数组,这里要触发调用,就需要传递进去一个数组参数。
(实际上这里不管数组中有没有参数都 OK ,哪怕只是一个 {} 都行!)
5、针对 Class 类中的成员方法的获取调用
同样,这里的方法也是在前置操作中实现过的,如图:
在这里,各种情况的方法都进行了创建。
这里获取成员方法需要用到的关键字和 main 方法使用的相同:Method
(1)获取所有 公有 的成员方法
// 首先,通过反射获取到 student 类
Class stuClass = Class.forName("Student");
// 创建出一个数组
Method[] methodArray = null;
// 首先获取所有的公有的成员方法
System.out.println("********获取所有的“公有”方法********");
methodArray = stuClass.getMethods();
for (Method m : methodArray) {
System.out.println(m);
}
观察运行结果:
在这里,很多隐式的方法都会被获取到后罗列出来,
红色范围 是我们自己所创建出的方法。(其中包含了 main 和 toString)
黄色范围 才是我们创建的成员方法。(是我们在这里需要的答案)。
(2)获取 “所有属性” 的成员方法
// 获取所有的方法,包括 私有方法 在内
System.out.println("********获取所有类型的 成员方法,包括私有类型********");
methodArray = stuClass.getDeclaredMethods();
for (Method m : methodArray) {
System.out.println(m);
}
观察运行结果:
在这里,红色区域 是我们创建出的成员方法。(是这里我们想要的结果)
(3)获取 “公有” 的成员方法并尝试调用
// 获取公有的 show1 方法
System.out.println("********获取所有的“公有”的 show1 方法********");
Method m = stuClass.getMethod("show1", String.class);
System.out.println("这是 show1 方法: " + m);
// 实例化 student 类
Object obj1 = stuClass.getConstructor().newInstance();
// 为 show1 方法添加上参数
m.invoke(obj1,"这是成员方法 String 参数的测试");
观察运行结果:
这里的获取、调用的方式和之前的 main 方法的调用和获取是相同的,这里就不过多赘述了。
(4)获取 私有 的成员方法并尝试调用
// 获取私有的 show2 方法
System.out.println("********获取所有的“私有”方法 show4 ********");
Method pm = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(pm);
// 要调用这个方法就需要解除私有限定
pm.setAccessible(true);
Object obj2 = stuClass.getConstructor().newInstance();
Object re = pm.invoke(obj2, 100);
System.out.println("私有方法 String 的返回值: " + re);
观察运行结果:
总的来讲,这里同样的可以分为四点:
- 要获取 所有 的 “公有” 的成员方法 —— getMethods()
- 要获取 所有 的 “各种属性” 的成员方法 —— getDeclaredMethods()
这两种获取需要注意的是,得到的元素需要通过 Field 类型的数组来进行接受)
- 要获取 一个 “公有” 的成员方法 —— getMethod(“方法名称”, 参数类型.class)
要获取 一个 “私有” 的成员方法 —— getDeclaredMethod(“方法名称”, 参数类型.class)
这里针对成员方法的调用流程在进行一下简单的描述:
首先通过 getConstructor().newInstance() 实例化一个对象。
之后通过 invoke(obj2, 100) 添加参数来调用方法。
6、实现通过反射运行 txt 配置文件的内容
要实现这个操作,首先需要做前置工作。也就是创建相应的软件包以及相应的 txt 文件,如图:
在这里的 txt 文件中,我们存放的是键值对类型的信息,如图:
前面的 key 名字可以随便起,但是后面的 value 必须是 方法名。
// ------通过反射运行配置文件内容------
public void show(int a) {
System.out.println("this is show()" + a);
}
实现核心的反射代码如下:
// 首先实现一个获取 test.txt 文件的方法
public static String getValue(String key) throws Exception{
Properties pro = new Properties(); // 获取配置文件的对象
FileReader in = new FileReader("src/txtpackage/test.txt"); // 获取输入流
pro.load(in); // 将流加载到配置文件对象中
in.close();
return pro.getProperty(key); // 根据 key 返回 value 的值
}
// 通过反射运行配置文件内容
public static void main(String[] args) throws Exception{
// 首先通过反射获取 Class 对象
Class stu = Class.forName("Student");
// 获取到 stu 中的 show 方法
Method m = stu.getMethod(getValue("methodName"), int.class); // 这里的 value 值对应的就是 show
// 调用 show 方法
m.invoke(stu.getConstructor().newInstance(), 1);
}
这里的 getValue() 方法主要是实现对于 txt 文件中的数据流进行获取,通过 main 方法中传递过来的 key 找到对应的 value 进行返回。
这里 main 方法中的实现就很好解释:
就是获取 成员方法 的基本操作
(没有参数的方法更好获取,只需要不填写后面的 int.class 以及 对应的 参数 1 值 即可)
运行结果如下:
7、实现通过 反射 越过泛型检查
直接代码如下:
// 这个类实现的是通过反射翻越泛型检查
public static void main(String[] args) throws Exception{
ArrayList<String> str = new ArrayList<>();
str.add("aaa");
str.add("bbb");
//str.add(100);
// 在这里的 str 中只能插入 String 类型的数据,但是通过反射,就可以将 100 这个 int 类型的元素添加进去
// 这样就做到了跳过 泛型检查
Class strClass = str.getClass(); // 这里是要找到 str 的字节码
Method m = strClass.getMethod("add", Object.class);
// 调用其中的 add 方法,并且向其中添加整形类型的数据
m.invoke(str, 100);
// 遍历参数
for (Object o : str) {
System.out.println(o);
}
}
可以看到,这里我们创建的是一个 String 类型的顺序表。要添加元素使用 .add() 方法进行添加即可。
如图我们要添加 整形 元素,是不能直接添加的。
先来观察运行结果:
可以看到,这里尽然将 100 这个整形元素添加了进去!!!
重点就是在这里,通过反射的形式调用其 add 方法。直接就越过了泛型的类型检查操作!
三、总结
反射的实际运用对于初学者可能并不多,但是也是必须要有简单的了解的。
本篇文章主要解释了反射中对于类中的 (构造方法、成员方法、单个字段) 的 获取、调用 操作。总的来说,也就是三个最核心的关键字 Constructor、Method、Field
最后还需要注意一点,获取 单个 和 多个方法的关键 API 非常相似,但是也并不容易搞错就是一个 “s” 之差!!
码子不易,您小小的点赞是对我最大的鼓励!!!