共计 5746 个字符,预计需要花费 15 分钟才能阅读完成。
反射是 Java 的一个高级个性,大量用在各种开源框架上。
在开源框架中,往往以同一套算法,来应答不同的数据结构。比方,Spring 的依赖注入,咱们不必本人 new 对象了,这工作交给 Spring 去做。
然而,咱们要 new 一个对象,就得写在代码上。但 Spring 必定猜不到咱们的类叫什么名字,那 Spring 又是怎么把对象给 new 进去的呢?
这就离不开反射。
反射的意义与作用
Java 有两种操作类的形式,别离是:非反射、反射。
先来说第一种形式,非反射。
非反射,就是依据代码,动态地操作类。比方,上面这段代码:
public class Application {public static void main(String[] args) { | |
// 创立一个用户对象 | |
ClientUser client = new ClientUser();} | |
} |
这个 main() 办法很简略,就是创立一个用户对象。整个过程是这样的,在 JVM 运行前,你必须先想好要创立哪些对象,而后写在代码上,最初你运行 main() 办法,JVM 给你创立一个用户对象。
简略来说,你写好代码,扔给 JVM 运行,运行完就没了。
在这种状况下,程序员必须管制所有,创立什么对象得提前写死在代码上。比方,我要多创立一个商户对象,那就得改代码:
public class Application {public static void main(String[] args) { | |
// 创立一个用户对象 | |
ClientUser client = new ClientUser(); | |
// 创立一个商户对象 | |
ShopUser shop = new ShopUser(); | |
// 省略有数 new 操作 | |
} | |
} |
如果依照这种做法,只有需要一变,程序员就得改代码,工作效率很低。比如说,你碰上简单些的我的项目,不光得创建对象,还得 set 成员变量。这样一来,每新加一个对象,你就得改一堆代码,迟早得累死。
那这些工作能简化吗?
这要用到第二种操作类的形式,反射。反射是一种动静操作类的机制。比方,我要创立一堆对象,那不必提前写死在代码,而是放在配置文件或者数据库上,等到程序运行的时候,再读取配置文件创建对象。
还是下面的代码,通过反射的革新,就变成这个样子:
public class Application { | |
// 模仿配置文件 | |
private static Set<String> configs = new HashSet<>(); | |
static {configs.add("com.jiarupc.reflection.ShopUser"); | |
configs.add("com.jiarupc.reflection.ClientUser"); | |
// 省略有数配置 | |
} | |
public static void main(String[] args) throws Exception { | |
// 读取配置文件 | |
for (String config : configs) { | |
// 通过配置文件,获取类的 Class 对象 | |
Class clazz = Class.forName(config); | |
// 创建对象 | |
Object object = clazz.newInstance(); | |
System.out.println(object); | |
} | |
} | |
} |
当你运行 main() 办法的时候,程序会先读取配置文件,而后依据配置文件创建对象。用了反射后,你有没有发现,工作变轻松了。一旦新加对象,咱们只有加一行配置文件,不必动其它中央。
看到这儿,你是不是想起某些开源框架?比方,Spring 的依赖注入。
// 加上一行注解,Spring 就接管这个类的创立工作 | |
@Service | |
public class UserService {// 省略业务代码...} |
你在某个类上加一行注解(这相当于加一行配置),Spring 就帮你接管这个类,你不必操心怎么创建对象了。而 Spring 之所以能接管你这个类,是因为利用了 Java 的反射。
简略来说,咱们如果用好反射,能缩小大量反复的代码。
接下来,咱们来看看反射能做什么吧~
反射获取类信息
如果你想操作一个类,那得晓得这个类的信息。比方,有哪些变量,有哪些结构器,有哪些办法 … 没有这些信息,你连代码都没法写,更别谈反射了。
限于篇幅,咱们次要讲怎么获取类的 Class 对象、成员变量、办法。
Class 对象只有 JVM 能力创立,外面有一个类的所有信息,包含:成员变量、办法、结构器等等。
换句话说,创立 Class 对象是 JVM 的事,咱们不必管。但想通过反射来操作一个类,得先拿到这个类的 Class 对象,这有三种形式:
1. Class.forName("类的全限定名") | |
2. 实例对象.getClass() | |
3. 类名.class |
你能够看上面的代码:
public class User {public static void main(String[] args) throws Exception {// 1. Class.forName("类的全限定名") | |
Class clazz1 = Class.forName("com.jiarupc.reflection.User"); | |
// 2. 实例对象.getClass() | |
User user = new User(); | |
Class clazz2 = user.getClass(); | |
// 3. 类名.class | |
Class clazz3 = ClientUser.class; | |
} | |
} |
当你通过这三种形式,拿到 Class 对象后,就能够用反射获取类的信息了。
Field 对象代表类的成员变量。咱们想拿到一个类的成员变量,能够调用 Class 对象的四个办法。
1. Field getField(String name) - 获取公共字段 | |
2. Field[] getFields() - 获取所有公共字段 | |
3. Field getDeclaredField(String name) - 获取成员变量 | |
4. Field[] getDeclaredFields() - 获取所有成员变量 |
咱们尝试下获取所有成员变量,代码逻辑是这样的:通过 User 类的全限定名,获取 Class 对象,而后调用 getDeclaredFields() 办法,拿到 User 类的全副成员变量,最初把变量名、类型输入到控制台。
public class User { | |
// 惟一标识 | |
private Long id; | |
// 用户名 | |
private String username; | |
public static void main(String[] args) throws Exception { | |
// 获取类的 Class 对象 | |
Class clazz = Class.forName("com.jiarupc.reflection.User"); | |
// 获取类的所有成员变量 | |
Field[] fields = clazz.getDeclaredFields(); | |
for (Field field : fields) {String msg = String.format("变量名:%s, 类型:%s", field.getName(), field.getType()); | |
System.out.println(msg); | |
} | |
} | |
} |
Method 对象代表类的办法。要拿到一个类的办法,Class 对象同样提供了四个办法:
1. Method getMethod(String name, Class[] params) - 通过办法名、传入参数,获取公共办法 | |
2. Method[] getMethods() - 获取所有公共办法 | |
3. Method getDeclaredMethod(String name, Class[] params) - 通过办法名、传入参数,获取任何办法 | |
4. Method[] getDeclaredMethods() - 获取所有办法 |
同样的,咱们尝试下获取所有办法,先通过 User 类的全限定名,获取 Class 对象,而后调用 getDeclaredMethods() 办法,拿到 User 类的全副成员办法,最初把办法名、形参数量输入到控制台。
public class User { | |
// 惟一标识 | |
private Long id; | |
// 用户名 | |
private String username; | |
public static void main(String[] args) throws Exception { | |
// 获取类的 Class 对象 | |
Class clazz = Class.forName("com.jiarupc.reflection.User"); | |
// 获取类的所有办法 | |
Method[] methods = clazz.getDeclaredMethods(); | |
for (Method method : methods) {String msg = String.format("办法名:%s, 形参数量:%s", method.getName(), method.getParameterCount()); | |
System.out.println(msg); | |
} | |
} | |
} |
看到这儿,你应该能晓得:怎么通过反射获取类的信息。
首先,获取类的 Class 对象有三种形式;而后,获取类的成员变量,这对应着 Field 对象;最初,获取类的办法,这对应着 Method 对象。
然而,反射不止能拿到类的信息,还能操作类。
反射操作类
反射能玩出很多花色,但 我认为最重要的是:创建对象和调用办法。
创建对象是所有的前提。对反射来说,如果没有创建对象,那咱们只能看看这个类的信息。比方,有什么成员变量,有什么办法之类的。而如果你想操作一个类,那么第一步就是创建对象。
你想要创建对象,必须调用类的结构器。这分为两种状况,最简略的是:你写了一个类,但没有写结构器,那这个类会自带一个无参的结构器,这就好办了。
public class User { | |
// 惟一标识 | |
private Long id; | |
// 用户名 | |
private String username; | |
public static void main(String[] args) throws Exception { | |
// 获取类的 Class 对象 | |
Class clazz = Class.forName("com.jiarupc.reflection.User"); | |
// 创建对象 | |
Object object = clazz.newInstance(); | |
System.out.println(object); | |
} | |
} |
咱们先获取类的 Class 对象,而后调用 newInstance()。
但还有一种状况,我不必 Java 自带的结构器,而是本人写。这种状况会简单一些,你得指定传入参数的类型,先拿到结构器,再调用 newInstance() 办法。
public class User { | |
// 惟一标识 | |
private Long id; | |
// 用户名 | |
private String username; | |
// 结构器 1 | |
public User(Long id) { | |
this.id = id; | |
this.username = null; | |
} | |
// 结构器 2 | |
public User(Long id, String username) { | |
this.id = id; | |
this.username = username; | |
} | |
public static void main(String[] args) throws Exception { | |
// 获取类的 Class 对象 | |
Class clazz = Class.forName("com.jiarupc.reflection.User"); | |
// 通过传入参数,获取结构器,再创建对象 | |
Constructor constructor = clazz.getConstructor(Long.class, String.class); | |
Object object = constructor.newInstance(1L, "jiarupc"); | |
System.out.println(object); | |
} | |
} |
咱们要在一开始就设置 id 和 username,那么你得传入参数的类型,先找到 结构器 2 -constructor
;而后,传入 id 和 username 到 constructor.newInstance()
办法,就能失去一个用户对象。
当拿到结构器,并创立好对象后,咱们就能够调用对象的办法了。
调用对象的办法分为两步:第一步,找到办法;第二步,调用办法。这听起来是非常简单,事实上也非常简单。你能够看上面的代码。
public class User { | |
// 惟一标识 | |
private Long id; | |
// 用户名 | |
private String username; | |
// .. 疏忽 set/get 办法 | |
public static void main(String[] args) throws Exception { | |
// 获取类的 Class 对象 | |
Class clazz = Class.forName("com.jiarupc.reflection.User"); | |
// 创建对象 | |
Object object = clazz.newInstance(); | |
System.out.println(object); | |
// 通过办法名、传入参数,找到办法 -setUsername | |
Method method = clazz.getMethod("setUsername", String.class); | |
// 调用 object 对象的 setUsername() 办法 | |
method.invoke(object, "JerryWu"); | |
// 输入所有成员变量 | |
Field[] fields = clazz.getDeclaredFields(); | |
for (Field field : fields) {String msg = String.format("变量名:%s, 变量值:%s", field.getName(), field.get(object)); | |
System.out.println(msg); | |
} | |
} | |
} |
咱们通过办法名 -setUsername、参数类型 -String,找到 setUsername 办法;而后,传入参数 -username 到 method.invoke(),执行 setUsername 办法;最初,输入所有成员变量,验证一下后果。
写在最初
反射是一种动静操作类的机制,它有两个用途。
第一个用途,通过反射,咱们能够拿到一个类的信息,包含:成员变量、办法、结构器等等。
第二个用途,通过反射,咱们能够操作一个类,包含:创建对象、调用对象的办法、批改对象的成员变量。
因为框架要以同一套算法,来应答不同的数据结构。所以,开源框架大量用到了反射。比方,Spring 的依赖注入就离不开反射。