本人大号:秃秃爱健身https://blog.csdn.net/Saintmm,系原创啦。
一、什么是反射?
1)Java反射机制的核心:是在程序运行时动态加载类并获取类的一些信息,从而操作类或对象的属性和方法。本质是在JVM得到class对象之后,通过class对象进行反编译,从而获取对象的各种信息。
2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
二、反射的原理?
下图为简易的类加载流程和反射的本质:
关于类的详细加载流程大家可以参阅官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
三、反射的优缺点
优点:
- 功能强大,在运行时能获得类的各种内容,然后进行反编译,这就可以让我们灵活的构建代码,使其进行运行时装配,并且无需在新增或者移除组件时进行源代码修改,即可以更容易的实现面向对象编程。
缺点:
- 大家都知道的反射会消耗一定的系统资源;因此在不需要动态代理对象的时候不建议使用反射。
(2)破坏封装性,导致安全问题,因为反射调用方法时可以忽略权限检查。
四、反射的用途
1、反编译:.class–>.java
2、通过反射机制访问java对象的属性,方法,构造方法等
3、IDE工具,比如Intellij IDEA,当我们输入一个对象或者类后,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,它们会需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
五、反射的用法
Java反射包:
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html
0)反射机制常用的类:
- Java.lang.Class;
- Java.lang.reflect.Constructor;
- Java.lang.reflect.Field;
- Java.lang.reflect.Method;
官方文档参考:
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
1)class的基本使用
1、获取class对象的三种方法
- Object#getClass()方法
https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html
- 任何数据类型都有一个“静态”的class属性
- **通过class类的静态方法:forName(String className)
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
代码:
package com.saint.reflect;
/**
* @author 周鑫(玖枭)
*/
public class ObtainClass {
public static void main(String[] args) {
// 这里的new操作会产生一个User对象和一个User.class对象
User user = new User();
// todo: 第一种方式获取Class对象
Class<? extends User> userClass = user.getClass();
System.out.println("user.getClass(): " + userClass.getName());
System.out.println("-----------------------");
// todo: 第二种方式获取Class对象
Class<User> userCLass2 = User.class;
System.out.println("User.class : " + userCLass2.getName());
System.out.println(userCLass2 == userClass);
System.out.println("-----------------------");
// todo:第三种方式获取Class对象
try {
Class<?> userClass3 = Class.forName("com.saint.reflect.User");
System.out.println("Class.forName() : " + userClass3.getName());
System.out.println(userClass3 == userClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
控制台输出内容:
user.getClass(): com.saint.reflect.User
-----------------------
User.class : com.saint.reflect.User
true
-----------------------
Class.forName() : com.saint.reflect.User
true
从控制台输出来看,我们可以知道一个类的Class对象是唯一的,因为类加载器确保类只会被加载一次(此处不考虑热部署中不同类加载器加载类的特例)。
在上述三种方式中:第一种对象都有了还需要反射干嘛;第二种需要导入类包,依赖太强,不导包就抛编译错误;最常用的是Class.forName()方法,参数可以通过配置文件导入,比较灵活,并且可以指定不同的类加载器,比如在加载JDBC驱动时:
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
2、通过反射创建实例的两种方式
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
- 先通过Class对象获取指定的Constructor构造器对象,再调用Constructor对象的newInstance()方法来创建Class对应的实例;这种方式可以指定的构造器来构造类的实例。
代码:
(1)User类
@Getter
@Setter
@ToString
public class User {
private long userId;
private String userName;
private String password;
//无参构造方法
public User() {
System.out.println("调用了公有、无参构造方法执行了。。。");
}
//有多个参数的构造方法
public User(long userId, String userName, String password) {
this.userId = userId;
this.userName = userName;
this.password = password;
System.out.println("编号:" + userId + "姓名:" + userName + "密码:" + password);
}
//有一个参数的构造方法
public User(String userName) {
this.userName = userName;
System.out.println("姓名:" + userName);
}
//受保护的构造方法
protected User(long userId) {
System.out.println("受保护的构造方法 userId = " + userId);
}
//私有构造方法
private User(long userId, String userName) {
this.userId = userId;
this.userName = userName;
System.out.println("私有的构造方法 编号:" + userId + ", 姓名:" + userName);
}
}
(2)创建实例的方式:
// todo: 第一种方式:使用Class.newInstance()方法
Class<User> clazz = User.class;
User newInstance = clazz.newInstance();
System.out.println("使用Class.newInstance()创建的实例: " + newInstance.toString());
// todo: 第二种方式:通过Class对象获取指定的Constructor构造器对象
Constructor constructor = clazz.getConstructor(String.class);
System.out.println("-----------------");
//根据构造器创建实例:
Object obj = constructor.newInstance("saint");
User newInstance2 = (User) obj;
System.out.println("使用构造器创建的实例: " + newInstance2.toString());
控制台输出:
调用了公有、无参构造方法执行了。。。
使用Class.newInstance()创建的实例: User(userId=0, userName=null, password=null)
-----------------
姓名:saint
使用构造器创建的实例: User(userId=0, userName=saint, password=null)
3、使用反射获取构造方法并使用
(1)批量获取的方法:
- public Constructor[] getConstructors():获取Class所有"public(公用)"的构造方法;
- public Constructor[] getDeclaredConstructors():获取Class所有的构造方法(包括private私有、protected受保护、public公有、默认);
(2)获取指定入参的单个构造器:
- public Constructor getConstructor(Class… parameterTypes):获取指定入参的“public”(共有)构造方法,非public类型的获取不到。
- public Constructor getDeclaredConstructor(Class… parameterTypes):不管构造方法是private私有的、protected受保护、默认、public公有的,只要存在我就可以根据入参获取到某个构造器。
(3)使用构造器创建实例(上面提到过):
- Constructor#newInstance(Object… initargs),它的返回类型是泛型T
官方的解释为&