1、反射概述
反射是Java的特征之一。反射的核心是JVM在
运行时
才会去动态加载类,调用方法,访问属性,它甚至不需要事先知道具体的对象是谁
- 通过反射,我们可以在程序
运行时
获取到类的信息,包括类的成员变量和方法- 此外,还能够在
运行时
任意调用该类的方法,构造这个类的对象等等操作- 总之,在反射面前,类就是 “透明” 的,没有任何 “隐私” 可言
- 甚至在编译时期,字节码不存在的类也可以被加载进来
反射在Java开发中的应用非常广泛,甚至IDEA的提示也用到了反射。来看看下面的示例吧:
1、创建一个properties配置文件
:
fullClassPath =javase.reflection.QiYu
method = leisurePunch
2、创建一个类
public class QiYu {
public String name = "QiYu";
public QiYu() {
System.out.println("QiYu,realHero诞生!");
}
public QiYu(String food) {
System.out.println("QiYu爱吃" + food);
}
public void leisurePunch() {
System.out.println("我是QiYu,现在我打出的是随意一拳!");
}
public void SeriousPunch() {
System.out.println("我是QiYu,现在我打出的是认真一拳!");
}
}
3、测试程序
:
public class ReflectionTest {
public static void main(String[] args) throws Exception {
// 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\javase\\reflection\\config.properties"));
// 获取配置文件中的信息
String fullClassPath = properties.getProperty("fullClassPath").toString();
String methodName = properties.getProperty("method").toString();
// 根据全类路径加载类
Class clz = Class.forName(fullClassPath);
// 通过反射获取到该类的实例
Object o = clz.newInstance();
System.out.println("获取到了类实例: " + o);
// 通过反射获取到该类中的方法,并执行
Method method1 = clz.getMethod(methodName);
System.out.println("方法开始执行: ");
method1.invoke(o);
// getField不能得到私有的属性
Field nameField = clz.getField("name");
// 通过set可以更改属性
nameField.set(o, "埼玉");
// 通过反射获取到类中的成员变量: Field对象.get(类的实例对象)
System.out.println("获取到了成员变量: " + nameField.get(o));
Constructor constructor1 = clz.getConstructor();
System.out.println("获取到了无参构造方法" + constructor1);
Constructor constructor2 = clz.getConstructor(String.class);
System.out.println("获取到了有参构造方法" + constructor2);
}
}
4、结果
:
QiYu,realHero诞生!
获取到了类实例: javase.reflection.QiYu@1540e19d
方法开始执行:
我是QiYu,现在我打出的是随意一拳!
public java.lang.String javase.reflection.QiYu.name
获取到了成员变量: 埼玉
获取到了无参构造方法public javase.reflection.QiYu()
获取到了有参构造方法public javase.reflection.QiYu(java.lang.String)
通过上面的反射入门案例,我们能够体会到:通过反射,我们可以在不修改源代码的情况下来控制程序
( 只需要更改配置文件,比如上述的properties.method
可以换成SeriousPunch
,程序的执行又会是另外的结果 )
将我们开头对于反射的介绍更具体一点的话,就是反射机制允许我们在程序运行期间
借助于Reflection
中的API来取得任何类的内部信息:如成员变量,构造器,成员方法等。并且能够操作对象的属性及方法。
我们都知道,当JVM加载完类之后,在堆中会产生一个Class类型的对象 (一个Class对象对应一个类) ,通过这个Class对象我们可以得知到整个类的结构,就像是一面镜子一样。所以形象的称之为:反射。这也是为什么在开头说在反射面前,类没有任何 “隐私” 可言。
与反射相关的主要类
:
类名 | 作用 |
---|---|
java.lang.Class | 代表一个类,Class对象表示某个类加载后在堆中的对象 |
java.lang.reflect.Method | 代表类的方法,Method对象就表示某个类的方法 |
java.lang.reflect.Field | 代表类的成员变量。Field对象就表示某个类的成员变量 |
java.lang.reflect.Constructor | 代表类的构造方法,Constructor对象就表示某个类的构造器 |
反射的优点:非常灵活,可以让我们动态地创建和使用对象,是框架的核心底层
反射的缺点:使用反射对执行速度有影响,因为反射基本是解释执行
反射调用优化-关闭访问检查
:
Method、Field、Constructor 对象都继承了
AccessibleObject
类,这意味着这三个对象都有setAccessible()
方法,这个方法的作用是启动/禁用 访问安全检查的开关
:true表示反射的对象在使用时取消访问检查,提高反射效率;false表示反射的对象执行访问检查
(确实能优化,但是只能优化一点)
2、反射的使用
【补充】
class类
:
- Class也是类,父类也是Object类
- Class类的对象不能new,而是由系统创建
- 每个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都知道自己是哪个Class类的实例
- 通过Class对象中的API可以得到一个完整的类的结构
- Class对象是存放在堆中的
- 类的字节码二进制数据是放在方法区中的,有的地方称为类的元数据
【常用方法】
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名name的Class对象 |
Object newInstance() | 调用缺省的构造函数,并返回该Class对象的一个实例 |
getName() | 返回该Class对象所表示的实体名称 |
Class[] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 获取该类的类加载器 |
Class getSuperclass() | 获取表示该Class所表示的实体的超类的Class |
Constructor[] getConstructors() | 获取包含某些Constructor对象的数组 |
Field[] getDeclaredFields() | 获取Field对象的一个数组 |
Method getMethod(String name,Class ... paramType) | 根据名称获取一个Method对象 |
2.1 获取Class对象
方法1:通过 实例.getClass() 方法直接获得。
Class clz1 = qiyu.getClass()
方法2:使用Class类的forName静态方法。需要抛出异常
Class clz2 = Class.forName("com.yao.javase.reflection.QiYu")
方法3:通过 类名.class 直接获得。该方式最安全,性能也最高
Class<QiYu> clz3 = QiYu.class
2.2 创建实例
方式1:使用Class对象的newInstance()
方法来创建Class对象对应类的实例。
// 根据全类路径加载类,fullClassPath是已配置的信息
Class clz = Class.forName(fullClassPath);
// 通过反射获取到该类的实例
Object o = clz.newInstance();
方式2:先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
// 获取String类的Class对象
Class s = String.class;
// 获取String类带一个String参数的构造器
Constructor constructor = s.getConstructor(String.class);
// 创建实例
Object o = constructor.newInstance("QiYu");
System.out.println(o);
2.3 判断是否为某个类的实例
方式1:可以通过instanceof
关键字来判断:
boolean flag = o instanceof QiYu;
System.out.println(flag); //true
方式2:也可以借助Class对象中的native方法isInstance()
方法来判断:
boolean flag = clz.isInstance(o);
System.out.println(flag); //true
2.4 获取方法
方法名 | 作用 |
---|---|
public Method[] getDeclaredMethods() throws SecurityException | 返回类或接口声明的所有方法,包括public、protected、default和private方法,但不包括继承下来的方法 |
public Method[] getMethods() throws SecurityException | 返回某个类的所有public方法,包括其继承类的公用方法 |
public Method[] getMethods(String name, Class<?>... parameterTypes) | 返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应的Class对象 |
2.5 获取构造器信息
与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个
newInstance
方法可以创建一个对象实例
2.6 获取类的成员变量
方法名 | 作用 |
---|---|
getDeclaredField() | 所有已声明的成员变量,但不能得到其父类的成员变量 |
getFiled() | 访问公有的成员变量 |
用法跟Method差不多
2.7 调用方法
当我们从类中获取了一个方法后,我们就可以用
invoke()
方法来调用:
public class Rectangle {
public double length;
public double width;
// 计算周长
public double circle() {
double count = this.length + this.width;
System.out.println("总和为: " + count);
return count;
}
}
public class ReflectionTest {
public static void main(String[] args) throws Exception{
// 获取到 Class对象
Class clz = Rectangle.class;
// 创造实例
Object o = clz.newInstance();
// 注入成员变量的值
Field field1 = clz.getField("length");
field1.set(o, 10.00);
Field field2 = clz.getField("width");
field2.set(o, 21.01);
// 获取方法
Method method = clz.getMethod("circle");
// 调用方法
method.invoke(o);
}
}
3、反射总结
我们需要注意
:
- 反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题
- 反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射