反射的思想及作用
有反必有正,就像世间的阴和阳,计算机的0和1一样。天道有轮回,苍天...(净会在这瞎bibi)
在学习反射之前,先来了解正射是什么。我们平常用的最多的 new
方式实例化对象的方式就是一种正射的体现。假如我需要实例化一个HashMap
,代码就会是这样子。
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 1);
复制代码
某一天发现,该段程序不适合用 HashMap 存储键值对,更倾向于用LinkedHashMap
存储。重新编写代码后变成下面这个样子。
Map<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 1);
复制代码
假如又有一天,发现数据还是适合用 HashMap来存储,难道又要重新修改源码吗?
发现问题了吗?我们每次改变一种需求,都要去重新修改源码,然后对代码进行编译,打包,再到 JVM 上重启项目。这么些步骤下来,效率非常低。
对于这种需求频繁变更但变更不大的场景,频繁地更改源码肯定是一种不允许的操作,我们可以使用一个开关
,判断什么时候使用哪一种数据结构。
public Map<Integer, Integer> getMap(String param) {
Map<Integer, Integer> map = null;
if (param.equals("HashMap")) {
map = new HashMap<>();
} else if (param.equals("LinkedHashMap")) {
map = new LinkedHashMap<>();
} else if (param.equals("WeakHashMap")) {
map = new WeakHashMap<>();
}
return map;
}
复制代码
通过传入参数param
决定使用哪一种数据结构,可以在项目运行时,通过动态传入参数决定使用哪一个数据结构。
如果某一天还想用TreeMap
,还是避免不了修改源码,重新编译执行的弊端。这个时候,反射就派上用场了。
在代码运行之前,我们不确定将来会使用哪一种数据结构,只有在程序运行时才决定使用哪一个数据类,而反射
可以在程序运行过程中动态获取类信息和调用类方法。通过反射构造类实例,代码会演变成下面这样。
public Map<Integer, Integer> getMap(String className) {
Class clazz = Class.forName(className);
Consructor con = clazz.getConstructor();
return (Map<Integer, Integer>) con.newInstance();
}
复制代码
无论使用什么 Map,只要实现了Map接口
,就可以使用全类名路径
传入到方法中,获得对应的 Map 实例。例如java.util.HashMap / java.util.LinkedHashMap····如果要创建其它类例如WeakHashMap
,我也不需要修改上面这段源码。
我们来回顾一下如何从 new
一个对象引出使用反射
的。
- 在不使用反射时,构造对象使用 new 方式实现,这种方式在编译期就可以把对象的类型确定下来。
- 如果需求发生变更,需要构造另一个对象,则需要修改源码,非常不优雅,所以我们通过使用
开关
,在程序运行时判断需要构造哪一个对象,在运行时可以变更开关来实例化不同的数据结构。 - 如果还有其它扩展的类有可能被使用,就会创建出非常多的分支,且在编码时不知道有什么其他的类被使用到,假如日后
Map
接口下多了一个集合类是xxxHashMap
,还得创建分支,此时引出了反射:可以在运行时
才确定使用哪一个数据类,在切换类时,无需重新修改源码、编译程序。
第一章总结:
- 反射的思想:在程序运行过程中确定和解析数据类的类型。
- 反射的作用:对于在
编译期
无法确定使用哪个数据类的场景,通过反射
可以在程序运行时构造出不同的数据类实例。
反射的基本使用
Java 反射的主要组成部分有4个:
Class
:任何运行在内存中的所有类都是该 Class 类的实例对象,每个 Class 类对象内部都包含了本来的所有信息。记着一句话,通过反射干任何事,先找 Class 准没错!Field
:描述一个类的属性,内部包含了该属性的所有信息,例如数据类型,属性名,访问修饰符······Constructor
:描述一个类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符······Method
:描述一个类的所有方法(包括抽象方法),内部包含了该方法的所有信息,与Constructor
类似,不同之处是 Method 拥有返回值类型信息,因为构造方法是没有返回值的。
我总结了一张脑图,放在了下面,如果用到了反射,离不开这核心的4
个类,只有去了解它们内部提供了哪些信息,有什么作用,运用它们的时候才能易如反掌。
我们在学习反射的基本使用时,我会用一个SmallPineapple
类作为模板进行说明,首先我们先来熟悉这个类的基本组成:属性,构造函数和方法
public class SmallPineapple {
public String name;
public int age;
private double weight; // 体重只有自己知道
public SmallPineapple() {}
public SmallPineapple(String name, int age) {
this.name = name;
this.age = age;
}
public void getInfo() {
System.out.print("["+ name + " 的年龄是:" + age + "]");
}
}
复制代码
反射中的用法有非常非常多,常见的功能有以下这几个:
- 在运行时获取一个类的 Class 对象
- 在运行时构造一个类的实例化对象
- 在运行时获取一个类的所有信息:变量、方法、构造器、注解
获取类的 Class 对象
在 Java 中,每一个类都会有专属于自己的 Class 对象,当我们编写完.java
文件后&#x