前言
初学时,反射、网络编程、IO是我最不能理解的三大模块。
当时查资料,给我的感觉是:写文章的都觉得我已经会了。
如今,我用实例尽可能简单地讲述反射这个东西。
能干啥?
人类文明分为三个阶段:
- 女娲创造人类
- 人类文明建立,地球产生秩序
- 人类灭亡
实例化
平常我们实例化一个类,都是用:
1Main main = new Main();
那如果要读取这个类中的公共变量,我们需要:
1public class Main {
2 public String value = "HelloWorld!";
3}
4
5public class Test {
6 public static void main(String[] args) {
7 Main main = new Main();
8 System.out.println(main.value);
9 }
10}
那么我们如果将这个变量设置为私有呢?
1public class Main {
2 private String value = "HelloWorld!";
3}
很明显,我们无法获得该变量的值,因为变量的作用域止步于自己的类中。
那么如果我们使用反射,我们就能获取到这个类的任何信息,无论它是私有的还是什么乱七八糟的,不但能看,还能改。
本章主要讲述方法的调用。修改值的方法八九不离十,在阅读本篇文章后,阅读其它文章将会很好理解。
所以...
回到那个人类文明的话题。
如果女娲完成了自己的任务,并且人类已经进入了文明时代后。
我们想操控所有人的思想和命运,怎么办?
对,上帝模式。
如果我们是上帝,我们就能掌握自己管辖区域内的任何物质,包括人类和大自然。
那么...
Java的反射就犹如上帝模式一样。我们一般使用Java反射可以用来:
1加载类;
2得到类的包名、父类和实现的接口;
3得到类默认方法的参数;
4得到类中的变量参数;
5修改类中的变量参数;
6得到类的方法名,方法的参数,方法的返回类型;
7调用类中的方法;
8得到类中的注解信息;
9得到类中的泛型信息;
10修改类中的数组;
11等等等等......
类的运行过程中,有些参数可能是不可控的。通过反射,我们可以深入掌握自己编写的类,同时在以后的项目和调试中也会有所应用。
咋用?
假设我们再写一个需求(伪代码):
1Class Values {
2 String value1 = "Hello";
3 String value2 = "World";
4}
5
6Class Main {
7 String str = "value2";
8}
假设变量str
的内容是不可控的,但str
的值一定是Values
类中的一个变量,我们想将制定str
值的变量修改... 应该怎么做呢?
首先,修改Values
类中的方法:
1public class Values {
2 public static String value1 = "Hello";
3 public static String value2 = "World";
4
5 public String getValue1() {
6 return value1;
7 }
8
9 public void setValue1(String value1) {
10 this.value1 = value1;
11 }
12
13 public String getValue2() {
14 return value2;
15 }
16
17 public void setValue2(String value2) {
18 this.value2 = value2;
19 }
20}
21
此时,如果我们想修改Value2
的值,只需执行Values
类中的方法setValue2(String value2)
即可。例:
1setValue2("NaN");
此时,Value2的值为“NaN”。
Brain Storm
如果你仔细阅读了上方的内容,你会发现反射支持一个特性:
1调用类中的方法。
那么,如果我们利用反射调用这个方法,就可行了。
编写Main.java
中的代码:
1import java.lang.reflect.Method;
2
3public class Main {
4 //将要调用的方法
5 private static String str = "setValue2";
6
7 public static void main(String[] args) {
8 try {
9 //执行Class.forName("类")时,JVM会查找对应的类并加载,这时JVM会执行该类的静态代码段。
10 Class clazz = Class.forName("Values");
11 /*
12 不使用 > Values values = new Values(); <
13 是因为 > Class.forName() < 是获取正在运行的实例,而 > new() < 会创建一个新的实例。
14 Values中的变量是静态的,所以它的值在启动时已经写入了内存,我们必须直接修改内存中的值,而不是再新建一个。
15 */
16 Values values = (Values) clazz.newInstance();
17 /*
18 getMethod(方法名, 传值1类型, 传值2类型, ...)
19 获取指定的方法对象
20 传值类型:String.class Integer.class 等等
21 */
22 Method method = clazz.getMethod(str, String.class);
23 System.out.println("修改前:" + Values.value2);
24 //调用指定方法的 > 方法对象.invoke(值实例, 传值1, 传值2, ...) <
25 method.invoke(values, "NaN");
26 System.out.println("修改后:" + Values.value2);
27 } catch (Exception E) {
28 E.printStackTrace();
29 }
30 }
31}
32
得到返回结果:
1修改前:World
2修改后:NaN
后语
这样,我们就相当于将字符动态转换为了一段可运行的代码,就能实现更多的功能了。
调用方法,修改值只是反射的一小部分作用。在将来的学习中你会学习到更多的使用姿势,帮助你完成更复杂的设计需求!