前言
反射(Reflection
),它是一种专门为静态语言提供的技术(反射并不是 Java 独有的,许多编程语言都提供了反射功能,例如:Go),用于在程序运行时动态地改变程序的状态和行为。
在程序运行状态中,给定任意一个对象都可以调用这个对象的属性、方法、构造方法,这种动态的获取类的信息和调用对象的方法的功能称之为 Java 的反射机制。
了解反射
主要功能
Java 反射机制主要提供了以下功能,这些功能都位于 java.lang.reflect
包。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
生成动态代理。
应用场景
- 很多框架(例如:Spring、JDBC)都是配置化的(例如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
- 注解作为一种标记,常常用于代替冗余复杂的配置文件(XML、properties),而反射是所有注解实现的原理。
可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
使用反射
众所周知,所有 Java 类均继承了 Object 类,在 Object 类中定义了一个
getClass()
方法,该方法返回一个 Class 类型的对象,而反射机制的实现主要通过操作java.lang.Class
类。
java.lang.reflect
包的核心接口和类如下:
Array
类:该类提供动态地生成和访问 Java 数组的方法。Constructor
类:提供一个类的构造函数的信息以及访问类的构造函数的接口。Field
类:提供一个类的域的信息以及访问类的域的接口。Member
接口:反映关于单个成员(字段或方法)或构造函数的标识信息。Method
类:提供一个类的方法的信息以及访问类的方法的接口。Modifier
类:提供了 static 方法和常量,对类和成员访问修饰符进行解码。Proxy
类:提供动态地生成代理类和类实例的静态方法。
在使用 Java 反射机制时,主要步骤包括:
- 获取目标类型的 Class 对象;
- 通过 Class 对象分别获取 Constructor 类对象、Method 类对象、Field 类对象;
- 通过 Constructor 类对象、Method 类对象、Field 类对象分别获取类的构造函数、方法、属性的具体信息,再进行后续操作。
获取 Class 对象
获取 Class 对象有三种方法。以下演示:
public class ReflectClassTest {
public static void main(String[] args) throws ClassNotFoundException {
// 1.通过字符串获取 Class 对象,这个字符串必须带上完整的路径名
Class clazz1 = Class.forName("myHomeWork.Exp5.Hero3");
System.out.println(clazz1.getCanonicalName());
// 2.通过类的 class 属性
Class clazz2 = Hero3.class;
System.out.println(clazz2.getCanonicalName());
// 3.通过对象的 getClass() 函数
Hero3 hero3Object = new Hero3();
Class clazz3 = hero3Object.getClass();
System.out.println(clazz3.getCanonicalName());
// 判断获取的 Class 对象是否相等
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
}
}
第一种方法应该不陌生,例如 JDBC 加载数据库驱动 Class.forName("oracle.jdbc.driver.OracleDriver")
,就使用类的完全限定名来反射对象的类。这种方法也最常用。
第二种方法需要导入类的包,依赖性太强,不导包就会抛出编译错误。
第三种方法都已经创建了一个对象,还需要反射吗?
最后的判断结果都是 true
,说明在运行期间,一个类只有一个 Class 对象产生。
获取构造方法
Class 对象提供以下方法获取对象的构造方法(Constructor):
getConstructor
:返回类的特定 public 构造方法。参数为方法参数对应 Class 的对象。getDeclaredConstructor
:返回类的特定构造方法。参数为方法参数对应 Class 的对象。getConstructors
:返回类的所有 public 构造方法。getDeclaredConstructors
:返回类的所有构造方法。
获取一个 Constructor 对象后,可以用 newInstance 方法来创建类实例。
先新建一个 Worker
类,其中设置了 6 个构造方法。
public class Worker {
// 默认的构造方法
Worker(String str) {
System.out.println("默认的构造方法 s = " + str);
}
// 无参构造方法
public Worker() {
System.out.println("调用了公有、无参构造方法执行了");
}
// 有一个参数的构造方法
public Worker(char gender) {
System.out.println("性别:" + gender);
}
// 有多个参数的构造方法
public Worker(String name, int age) {
System.out.println("姓名:" + name + ",年龄:" + age);
}
// 受保护的构造方法
protected Worker(boolean n) {
System.out.println("受保护的构造方法,n = " + n);
}
// 私有的构造方法
private Worker(int age) {
System.out.println("私有的构造方法,年龄:" + age);
}
}
以下演示:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author yyt
*/
public class ReflectConstructorTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1.加载Class对象
Class<?> clazz = Class.forName("myHomeWork.Exp5.Worker");
// 2.获取所有公有的构造方法
System.out.println("-----所有公有构造方法-----");
Constructor<?>[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("-----所有的构造方法-----");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("-----获取公有、无参的构造方法-----");
Constructor con = clazz.getConstructor(null);
System.out.println("con = " + con);
// 调用构造方法创建实例
Object obj = con.newInstance();
System.out.println("-----获取私有构造方法,并调用-----");
con = clazz.getDeclaredConstructor(int.class);
System.out.println(con);
// 暴力访问
con.setAccessible(true);
// 创建实例
obj = con.newInstance( 22);
}
}
以上设置了 con.setAccessible(true)
这一句,这是使用 Field 类、Method 类、Constructor 类对象的 setAccessible(flag)
来绕开访问限制,当 flag
为 true
时 ,表示已屏蔽 Java 语言的访问检查,使得反射可以访问、修改对象的私有属性、方法。
获取成员变量
Class 对象提供以下方法获取对象的成员(Field):
getFiled
:根据名称获取公有的(public)类成员。getFields
:获取所有公有的(public)类成员。getDeclaredField
:根据名称获取已声明的类成员。但不能得到其父类的类成员。getDeclaredFields
:获取所有已声明的类成员。
新建一个 Person
类,其中设置不同访问权限的属性:公有、受保护、默认、私有。
public class Person {
// 公有属性
public String name;
// 私有属性
protected int age;
char sex;
private String phoneNumber;
}
以下演示:
import java.lang.reflect.Field;
/**
* @author lenovo
* 反射获取属性、方法
*/
public class ReflectFieldsTest {
public static void main(String[] args) throws ClassNotFoundException {
// 获取反射对象
Class<?> person = Class.forName("myHomeWork.Exp5.Person");
// 获取公有的属性
Field[] fs1 = person.getFields();
if (fs1 != null){
System.out.println("获取的公有属性");
for (Field f : fs1){
System.out.println(f);
}
}
System.out.println("----------");
// 获取所有的属性
Field[] fs2 = person.getDeclaredFields();
System.out.println("获取所有的属性");
for (Field f : fs2){
System.out.println(f);
}
}
}
获取成员方法
Class 对象提供以下方法获取对象的方法(Method):
getMethod
:返回类或接口的特定方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。getMethods
:返回类或接口的所有 public 方法,包括其父类的 public 方法。getDeclaredMethod
:返回类或接口的特定声明方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。getDeclaredMethods
:返回类或接口声明的所有方法,包括 public、protected、默认(包)访问和 private 方法,但不包括继承的方法。
获取一个 Method 对象后,可以用 invoke
方法来调用这个方法。
新建一个 Programmer
类,其中声明了不同访问权限的成员方法。
public class Programmer {
// 构造函数
public Programmer(){
System.out.println("乾坤未定,你我皆是牛马");
}
// 成员方法
public void doSomething(){
System.out.println("调用了公有的、无参方法:doSomething()");
}
protected void show(){
System.out.println("调用了受保护的、无参的show()");
}
void writeBugs(int count){
System.out.println("调用了默认的、有参方法:writeBugs(int count):" + count);
}
private String sayHello(String msg){
System.out.println("调用了私有的、有返回值的,String 参数的 sayHello():msg = " + msg);
return "hello";
}
}
以下演示:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author yyt
* 反射获取成员方法并使用
*/
public class ReflectMethodTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, IllegalAccessException, InstantiationException {
// 1.获取 Class 对象
Class<?> clazzP = Class.forName("myHomeWork.Exp5.Programmer");
// 2.获取所有方法
System.out.println("-----获取所有的方法-----");
Method[] methods1 = clazzP.getDeclaredMethods();
for(Method m : methods1){
System.out.println(m);
}
// 3.获取所有 public 的方法
System.out.println("-----获取所有 public 的方法-----");
Method[] methods2 = clazzP.getMethods();
for(Method m : methods2){
System.out.println(m);
}
// 4.获取公有的特定方法
System.out.println("-----获取公有的特定方法-----");
// 获取声明的函数,传入所需参数的类名,如果有多个参数,用','连接即可
Method m = clazzP.getMethod("doSomething");
System.out.println(m);
// 实例化一个对象
Object obj = clazzP.getConstructor().newInstance();
// 利用 Method 的 invoke 方法调用 doSomething(),无返回值,输出为 null
System.out.println(m.invoke(obj));
// 5.获取私有的特定方法
System.out.println("-----获取私有的特定方法-----");
Method m2 = clazzP.getDeclaredMethod("sayHello", String.class);
System.out.println(m2);
// 因为是私有的方法,需要下面这一行代码使其可使用,公有的则不需要
m2.setAccessible(true);
// 需要两个参数,一个是要调用的对象(获取有反射),一个是实参
Object result = m2.invoke(obj, "This is a message.");
System.out.println("方法返回值:" + result);
}
}
使用 invoke(Object obj, Object... args)
方法调用,传入对象以及函数所需的参数,如果有多个参数,用英文逗号 ',' 连接即可。函数会返回一个 Object 对象,使用强制类型转换转成实际类型即可。
存在的问题
- 性能问题:由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差。
- 安全问题:众所周知,单例设计模式中会强调将构造器设计为私有,因为这样可以防止从外部构造对象。但是反射可以获取类中的域、方法、构造器,修改访问权限,破坏了封装性,导致 “private” 的意义何在?
- 检查问题:丧失了编译时类型检查的好处(通过反射可以越过泛型检查),包括异常检查。如果程序企图用反射去调用不存在或者不可访问的方法,在运行时将会失败。
对于“Java 的反射是否破坏了其封装性”这个问题,有两种不同的见解:
- Java 的
private
修饰符并不是为了安全性设计的,private
并不是解决“安全”问题的。private
想表达的不是“安全性”的意思,而是面向对象编程的封装概念,用户常规使用 Java 的一种约束。 - 如果一个类中的私有成员是为了服务那些对外提供的
public
成员,那么即使通过反射机制获取了类中私有成员的存取权限,恶意修改,也纯属于自己恶心自己。
参考
知乎专栏 - 力扣
Github - JavaCore
CSDN - 杨晨光
CSDN - 徐大侠0610