前言
反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,并能直接操作程序的内部属性和方法。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
本篇就让我们深入了解下 Java 的反射技术,共同探究 java 程序的运行机制。
反射的概念和原理
反射的概念是由 Smith 在 1982 年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。通俗地讲,一提到反射,我们就可以想到镜子。镜子可以明明白白地照出我是谁,还可以照出别人是谁。反映到程序中,反射就是用来让开发者知道这个类中有什么成员,以及别的类中有什么成员。
为什么要有反射
有人可能会疑惑,Java 已经有了封装为什么还要有反射呢?反射看起来会破坏封装性。甚至让私有变量都可以被外部访问到,使得类变得不那么安全了。
反射主要应用在以下几方面:
- 开发通用框架:像 Spring,为了保持通用性,通过配置文件来加载不同的对象,调用不同的方法。
- 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
- 注解:注解本身只是起到一个标记符的作用,它需要利用发射机制,根据标记符去执行特定的行为。
反射 API
Java 类的成员包括以下三类:属性字段、构造函数、方法。反射的 API 也是与这几个成员相关:
Field 类:提供有关类的属性信息,以及对它的动态访问权限。它是一个封装反射类的属性的类。
Constructor 类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类。
Method 类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的一个类。
Class 类:表示正在运行的 Java 应用程序中的类的实例。
Object 类:Object 是所有 Java 类的父类。所有对象都默认实现了 Object 类的方法。
接下来,我们通过一个典型的例子来学习反射。先做准备工作,新建 com.test.reflection 包,在此包中新建一个 Student 类:
package com.test.reflection;
public class Student {
private String studentName;
public int studentAge;
public Student() {
}
private Student(String studentName) {
this.studentName = studentName;
}
public void setStudentAge(int studentAge) {
this.studentAge = studentAge;
}
private String show(String message) {
System.out.println("show: " + studentName + "," + studentAge + "," + message);
return "testReturnValue";
}
}
图中可以看出,Student 类中有两个字段、两个构造方法、两个函数,且都是一个私有,一个公有。由此可知,这个测试类基本涵盖了我们平时常用的所有类成员。
获取Class对象的方法
1)通过类名创建Class对象
Class clz = Class.forName("java.lang.String");
2)通过类的实例对象的getClass()方法获取Class对象
String s = new String();
Class clz = s.getClass();
3)通过类的.class属性获取Class对象
Class clz = int.class;
实践
让我们实际写个代码例子:
// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.获取声明的构造方法,传入所需参数的类名,如果有多个参数,用','连接即可
Constructor studentConstructor = studentClass.getDeclaredConstructor(String.class);
// 如果是私有的构造方法,需要调用下面这一行代码使其可使用,公有的构造方法则不需要下面这一行代码
studentConstructor.setAccessible(true);
// 使用构造方法的newInstance方法创建对象,传入构造方法所需参数,如果有多个参数,用','连接即可
Object student = studentConstructor.newInstance("NameA");
// 3.获取声明的字段,传入字段名
Field studentAgeField = studentClass.getDeclaredField("studentAge");
// 如果是私有的字段,需要调用下面这一行代码使其可使用,公有的字段则不需要下面这一行代码
// studentAgeField.setAccessible(true);
// 使用字段的set方法设置字段值,传入此对象以及参数值
studentAgeField.set(student,10);
// 4.获取声明的函数,传入所需参数的类名,如果有多个参数,用','连接即可
Method studentShowMethod = studentClass.getDeclaredMethod("show",String.class);
// 如果是私有的函数,需要调用下面这一行代码使其可使用,公有的函数则不需要下面这一行代码
studentShowMethod.setAccessible(true);
// 使用函数的invoke方法调用此函数,传入此对象以及函数所需参数,如果有多个参数,用','连接即可。函数会返回一个Object对象,使用强制类型转换转成实际类型即可
Object result = studentShowMethod.invoke(student,"message");
System.out.println("result: " + result);
以上代码逻辑梳理如下:
- 先用第一种全路径获取 Class 的方法获取到了 Student 的 Class 对象
- 然后反射调用它的私有构造方法 private Student(String studentName),构建出 newInstance
- 再将其公有字段 studentAge 设置为 10
- 最后反射调用其私有方法 show,传入参数 “message”,并打印出这个方法的返回值。
其中,setAccessible 函数用于动态获取访问权限,Constructor、Field、Method 都提供了此方法,让我们得以访问类中的私有成员。
反射的优缺点
- 优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。
- 缺点:
- 正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。
- 相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低
- 破坏java类封装性,类的信息隐藏性和边界被破坏
本文由mdnice多平台发布
标签:反射,Java,String,构造方法,详解,方法,Class From: https://www.cnblogs.com/GentleJim/p/17500484.html