关于 JAVA 反射机制

关于 JAVA 反射机制


关于 JAVA 的反射机制,之前学的有点混:

  • 动态创建类、动态调用方法,这部分还比较好理解
  • 但关于 “编译依赖”、“运行依赖” 这部分,就有点云里雾里

最近在学框架,再次遇到这个问题,百思不得其解:

为什么 “编译依赖” 这么不受待见,问题不是越早发现越好吗?编译器报错,甚至 IDE 环境提前提示错误,难道不比等到运行的时候再发现错误更好?

于是一番查询检索,但绝大多数资料,都只是抄书似的介绍一下各个 API 的使用,真是毫无意义、却无比浪费时间的一锅信息粥。

最后想着试试找个课程翻翻看,在慕课网的这个专题课程1找到了解答


这其实是关于解耦合的概念,属于软件工程的范畴。

在进行项目开发的时候,功能拆分、模块化设计,可以使整个程序结构更有条理,有利于分工合作,有利于代码复用,有利于维护拓展,从而在多个方面提高效率。低耦合的设计,是重要,且非常必要的设计思想。

但如果只是简单地把功能拆分,各功能、模块,各个类之间还是直接地调用、直接地创建对象,那么,在某个位置发生代码改变的时候,牵一发而动全身,实际上可能需要修改大量其他各处的相关代码。这样的情况,属于 “编译依赖”。

而如果能实现:各个功能、模块、类内部修改代码,不会影响到其他功能、模块、类,这样才真正实现了模块化,可称为 “运行依赖”。

用这个课程中老师举的例子来说:

  • 现在有一个 Web 项目,有一个接口 “A” ,有多个实现类(A1, A2 …)交给不同的工程师开发,另外有一个 “B” 类,调用其中不同的实现类来实现不一样的功能。
  • “B”、“A”、“A1”,都已完成开发,“A2” 还没完成。
  • 如果处于 “编译依赖” 的情形,那么,只能等 “A2” 完成开发,再进行测试,或者注释掉 “B” 类中关于 “A2” 的相关代码,进行测试。
  • 如果达到了 “运行依赖” 的情形,则可以直接进行测试,只要不使用 “A2” 的链接、按钮就可以。

孰优孰劣,一目了然。这段内容,对 本X 而言,可称得上是 “醍醐灌顶”。听老师一席话,胜喝互联网十锅粥。

所以,反射机制非常重要。


其次,关于 “泛型” ,我们知道 “泛型只在编译阶段有效,而实际运行时,是不区分对象类型的” ,这其实也可以通过反射来证实:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.lang.reflect.Method;
import java.util.ArrayList;

public class GenericsReflection {

    public static void main(String[] args) {

        // 两个List,一个不限定类型,一个限定为String
        ArrayList list1 = new ArrayList();
        ArrayList<String> list2 = new ArrayList<>();

        list1.add("hello");
        list1.add(20);

        list2.add("hello");
        // list2.add(20);  // 编译报错:java: 不兼容的类型: int无法转换为java.lang.String

        // 初步测试:使用反射获取两个List的类型
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 输出结果为true


        // 进一步测试:通过反射机制绕过编译阶段的泛型检查
        try {
            System.out.println("操作前的 ArrayList<String> list2 :" + list2 + ",它的 size 是:" + list2.size());
            Method m = c2.getMethod("add", Object.class);
            m.invoke(list2, 20);    // 通过反射绕过编译阶段的泛型检查
            System.out.println("操作后的 ArrayList<String> list2 :" + list2 + ",它的 size 是:" + list2.size());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

测试中可以明显看到:

  • 直接向 String 泛型的 List 添加数值类型的 20 ,IDE 会提示,编译过程也会报错;
    • 20210705_a1.png
      关于反射(1.编译报错)
  • 但如果使用反射,可以绕过编译阶段的泛型检查,顺利编译运行。
  • 证实了 “泛型只在编译阶段有效,而实际运行时,是不区分对象类型的”

此时,如果使用 String 类型来进行 for each 遍历,顺利编译,但运行报错:

1
2
3
for (String string : list2) {
    System.out.println(string);
}
20210705_a2.png
关于反射(2.运行报错)

若使用 Object 类型,则能够正常遍历出来:

1
2
3
for (Object object : list2) {
    System.out.println(object);
}

相关内容