目录
首先我们先来看看反射的基本介绍吧
反射的基本介绍
Java的发射机制(Reflection)是指在运行时动态地获取类的信息并操作对象的能力。Java的发射机制允许程序在运行时检查和操作任意一个类、方法、属性等的信息,包括了类名、方法名、属性名、参数列表以及访问修饰符等。
通过Java的发射机制,可以实现一些高级的功能,比如动态生成代理对象、动态生成类、动态配置对象等。同时,Java的一些框架也广泛应用了发射机制,比如Spring框架中大量使用了反射机制来实现依赖注入和AOP等功能。
Java中一些常用的反射API包括Class、Method、Field、Constructor等。其中,Class类是反射机制的核心,通过它可以获取一个类的所有信息。Method和Field则分别表示类中的方法和属性,Constructor则表示类中的构造方法。
Java的发射机制给Java编程带来了更大的灵活性,但同时也需要注意到发射机制的运行效率相对较低,同时也可能破坏类的封装性,因此在使用时需要谨慎考虑。
在了解反射的基本介绍之后,我们在来看看反射的需求,为什么会需要反射
先看一段代码
代码演示:
package com.reflection.question;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 反射问题的引入
*/
@SuppressWarnings({"all"})
public class ReflectionQuestion {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//根据配置文件 re.properties 指定信息, 创建Cat对象并调用方法hi
//传统的方式 new 对象 -》 调用方法
// Cat cat = new Cat();
// cat.hi(); ===> cat.cry() 修改源码.
//我们尝试做一做 -> 明白反射
//1. 使用Properties 类, 可以读写配置文件 Properties的底层就是HashTable
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));//加载配置文件的键值对到properties对象
String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat" 根据键获取值
String methodName = properties.get("method").toString();//"hi"
System.out.println("classfullpath=" + classfullpath);
System.out.println("method=" + methodName);
//2. 创建对象 , 传统的方法,行不通 =》 反射机制
//new classfullpath();
//3. 使用反射机制解决
//(1) 加载类, 返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o的运行类型=" + o.getClass()); //运行类型
//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4) 通过method1 调用方法: 即通过方法对象来实现调用方法
System.out.println("=============================");
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
}
}
反射的需求
Java反射机制的需求主要有以下几个方面:
-
动态加载类和调用类方法:通过反射机制,可以在运行时动态的加载一个类,并调用该类的方法。这种方式可以实现更加灵活的代码编写,避免了在编译期就要确定所有的类和方法的限制。
-
实现通用框架:通过反射机制,可以实现通用的框架,比如ORM(对象-关系映射)框架、依赖注入框架、AOP(面向切面编程)框架等。这些框架需要在运行时动态的获取类的信息,然后进行相应的操作。
-
修改类的属性和方法:通过反射机制,可以在运行时修改类的属性和方法,从而实现更加灵活的代码编写。
-
实现序列化和反序列化:Java中的序列化和反序列化需要用到反射机制,通过反射机制可以获取对象的属性和方法,然后将其序列化为字节流或者反序列化为对象。
-
动态代理:通过反射机制,可以实现动态代理,即在运行时生成一个代理对象,通过代理对象来调用目标对象的方法,从而实现AOP等功能。
那么我们使用反射机制可以完成
1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时得到任意一个类所具有的成员变量和方法
4.在运行时调用任意一个对象的成员变量和方法
5.生成动态代理
反射相关的主要类
1.java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
2.java.lang.reflect.Method: 代表类的方法, Method对象表示某个类的方法
3.java.lang.reflect.Field: 代表类的成员变量,Field对象表示某个类的成员变量
4.java.lang.reflect.Constructor: 代表类的构造方法,Constructor对象表示构造器
代码演示:
注意:反射和我们正常的使用方式有些不同,我们正常调用方法时对象名.方法名,但是在反射中是 方法名.对象名这里要注意一下
package com.reflection;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
@SuppressWarnings({"all"})
public class Reflection01 {
public static void main(String[] args) throws Exception {
//1. 使用Properties 类, 可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"
String methodName = properties.get("method").toString();//"hi"
//2. 使用反射机制解决
//(1) 加载类, 返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o的运行类型=" + o.getClass()); //运行类型
//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4) 通过method1 调用方法: 即通过方法对象来实现调用方法
System.out.println("=============================");
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
//java.lang.reflect.Field: 代表类的成员变量, Field对象表示某个类的成员变量
//得到name字段
//getField不能得到私有的属性
Field nameField = cls.getField("age"); //
System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象)
//java.lang.reflect.Constructor: 代表类的构造方法, Constructor对象表示构造器
Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
System.out.println(constructor);//Cat()
Constructor constructor2 = cls.getConstructor(String.class); //这里传入的 String.class 就是String类的Class对象
System.out.println(constructor2);//Cat(String name)
}
}
反射优点和缺点
优点:
-
动态性:Java反射机制可以在运行时动态地获取类的信息和操作对象,使程序更加灵活和易于扩展。
-
通用性:可以通过反射机制操作任意类型的对象,无需知道对象的具体类型。
-
代码复用性:反射机制可以让代码更加通用和复用,提高开发效率。
-
AOP支持:反射机制是实现AOP(面向切面编程)的重要手段之一。
缺点:
-
性能较低:Java反射机制需要在运行时动态地获取对象信息和方法,需要一定的时间开销,因此性能较低。
-
安全问题:反射机制可以操作任意类型的对象,容易造成安全问题,需要谨慎使用,并在必要时进行安全检查。
-
可读性下降:由于反射机制支持动态的获取和操作对象,因此代码的可读性会下降一些,需要谨慎使用。
-
不利于编译器优化:由于反射机制需要在运行时动态获取对象信息和方法,因此编译器很难对代码进行优化。
代码演示;
在下面的代码中 ,我们分别测试了,使用正常的方式去调用方法,和使用反射的方式去调用方法,并且在执行方法开始之前记录了开始时间,在方法执行结束之后,记录了结束时间,最后两个相减,就可以得到方法的执行时间,我们就可以看出,使用正常的方式去调用方法,和使用反射的方式去调用方法,的区别。
package com.reflection;
import com.Cat;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 测试反射调用a的性能,和优化方案
*/
public class Reflection02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//Field
//Method
//Constructor
m1();
m2();
m3();
}
//传统方法来调用hi
public static void m1() {
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 90; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("m1() 耗时=" + (end - start));
}
//反射机制调用方法hi
public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("com.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//反射调用方法
}
long end = System.currentTimeMillis();
System.out.println("m2() 耗时=" + (end - start));
}
//反射调用优化 + 关闭访问检查
public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("com.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//反射调用方法
}
long end = System.currentTimeMillis();
System.out.println("m3() 耗时=" + (end - start));
}
}
反射调用优化
Java中反射调用通常比直接调用方法缓慢,因为它需要进行额外的动态类型检查和查找。为了优化反射调用,可以采用以下几种方法:
-
缓存Method对象:通过缓存Method对象,避免动态查找,提高调用效率。
-
使用方法句柄(MethodHandle):方法句柄是一种更高效的调用机制,它可以绕过反射调用的开销。方法句柄比反射调用更快,特别是在热代码路径上。
-
使用invokedynamic指令:为了提高反射调用的性能,Java 7引入了invokedynamic指令。它允许动态绑定方法调用,避免了反射调用的开销。
-
使用字节码生成库:字节码生成库可以生成字节码,从而避免了反射调用的开销。比如,cglib是一个非常常用的字节码生成库。