1、反射机制的概念
指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能操作它的任意一个方法和属性。这种动态获取信息,以及动态调用对象方法的功能叫Java语言的反射机制。
本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
2、反射的原理
为了彻底理解反射的原理,可以先理解一下虚拟机的工作机制。
通常,java在编译之后,会将Java代码生成为class源文件,JVM启动时,将会载入所有的源文件,并将类型信息存放到方法区中;将所有对象实例存放在Java堆中,同时也会保存指向类型信息的引用。
反射首先需要获取到这个对象存放在方法区的类型信息,获取到类型信息后,我们就知道这个类的构造器、属性、方法、注解、子类、父类等等信息了,这个时候,我们就可以通过这些类型信息来回调处理对象,来完成自己想要的操作了。
3、反射的优缺点
(1)优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
(2)缺点:
- 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
4、反射的用途
反射功能通常用于检查或修改Java虚拟机运行中(runtime)的应用程序的行为,这一句话就精准的描述了反射的全部功能,更详细来说可以分为以下几点:
1. 在运行中分析类的能力,可以通过完全限定类名创建类的对象实例。
2. 在运行中查看和操作对象,可以遍历类的成员变量。
3. 反射允许代码执行非反射代码中非法的操作,可以检索和访问类的私有成员变量,包括私有属性、方法等。
注意:要有选择的使用反射功能,如果可以直接执行操作,那么最好不要使用反射。
5、在Java中应用反射
1. 获取Class对象
(1)知道具体类的情况下可以使用
Class<String> aClass0 = String.class;
(2)通过 Class.forName()
传入类的全路径获取
Class aClass1 = Class.forName("cn.dailyTest.TargetObject");
(3)通过对象实例instance.getClass()
获取
TargetObject o = new TargetObject();
Class<? extends TargetObject> aClass1 = o.getClass();
(4)通过类加载器xxxClassLoader.loadClass()
传入类路径获取
Class clazz = ClassLoader.loadClass("cn.dailyTest.TargetObject");
通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
2. 通过反射机制去获取类的信息
首先创建一个类
public class TargetObject {
private String value;
public String name;
public TargetObject() {
value = "java";
}
public void publicMethod(String s) {
System.out.println("I love " + s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}
使用反射操作这个类的方法以及参数
public class 反射 {
public static void main(String[] args) throws Exception {
// 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
Class<?> targetClass = Class.forName("com.dailyTest.TargetObject");
System.out.println(targetClass);
TargetObject targetObject = (TargetObject) targetClass.newInstance();
// 获取 TargetObject 类中定义的所有方法
Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
// 获取指定方法并调用
Method publicMethod = targetClass.getDeclaredMethod("publicMethod", String.class);
publicMethod.invoke(targetObject, "反射");
// 获取指定参数并对参数进行修改
Field field = targetClass.getDeclaredField("value");
// 为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "fanShe!");
// 调用 private 方法
Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
// 为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
输出:
class com.dailyTest.TargetObject
privateMethod
publicMethod
I love 反射
value is fanShe!
3. 反射可获取类的所有信息
包括:modifier、constructor、field、method
4. 反射方法的其他使用--通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉),所以是可以通过反射越过泛型检查的。
public static void main(String[] args) throws Exception {
ArrayList<String> stringList = new ArrayList<>();
stringList.add("aaa");
stringList.add("bbb");
// stringList.add(100); // 这里报错,需要的类型是String,提供的类型是int
// 但是使用反射机制就能把100添加进去
Class<?> listClass = stringList.getClass();
Method add = listClass.getMethod("add", Object.class);
add.invoke(stringList, 100);
System.out.println(stringList);
for(Object o : stringList) {
System.out.println(o.getClass());
}
}
输出:
[aaa, bbb, 100]
class java.lang.String
class java.lang.String
class java.lang.Integer
思考:泛型是会擦除的,那为什么反射能够获取到泛型的信息呢?
分析:泛型的信息只存在编译阶段,在class字节码就看不到泛型的信息了。其实,可以理解为泛型擦除是有范围的,定义在类上的泛型信息是不会被擦除的。Java编译器仍在class文件以 Signature属性的方式保留了泛型信息。Type作为顶级接口,Type下还有几种类型,比如TypeVariable、ParameterizedType、WildCardType、GenericArrayType、以及Class。通过这些接口我们就可以在运行时获取泛型相关的信息。
思考:都说反射会影响性能,有什么方式可以减低它的性能影响吗?
分析:可以使用缓存把反射的元数据存储起来,下一次使用的时候就可以直接从内存获取了。尽可能使用高性能的反射框架。