Java反射
反射
1.反射概念
Java反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为JAVA语音的反射机制。
2.反射基础
RTTI(Run-Time Type Identification)运行时类型识别。在《Thinking in Java》一书第十四章中有提到,其作用是在运行时识别一个对象的类型和类的信息。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
这里我们首先需要理解 Class类,以及类的加载机制; 然后基于此我们如何通过反射获取Class类以及类中的成员变量、方法、构造方法等。
3.Class类
3.1 Class类对象的获取:
- 根据类名:类名.class
- 根据对象:对象.getClass(),一般用于参数传参
- 根据全限定类名:Class.forName(全限定类名),一般用于配置文件
4. 反射机制原理图:

5.哪些类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
6.类加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类,即时不存在该类,则不报错,降低了依赖性
- 类加载时机
- 当创建对象时(new):静态加载
- 当子类被加载,父类也加载:静态加载
- 调用类中的静态成员:静态加载
- 通过反射:动态加载
1.加载阶段:
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,生成一个代表该类的java.lang.Class对象
2.连接阶段:
2.1 验证:
- 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全
- 包括:文件格式验证(是否以魔数 oxcafebabe开头),元数据验证,字节码验证和符号引用验证
- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
2.2 准备:
- JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始化值,如0,0L,null,false等)。这些变量所使用的内存都在将在方法区中进行分配,但static final在准备阶段会一次性赋值。
2.3 解析:
- 虚拟机将常量池内的符号引用替换为直接引用的过程
3.Initalization(初始化)
- 到初始化阶段,才真正开始执行类中的定义的Java程序代码,此阶段是执行< clinit >()方法的过程。
- < clinit >()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
- 虚拟机会保证一个类的< clinit>()方法在多线程环境中正确地加锁,同步,如果多个线程同时去初始化一个类,那么只会一个线程去执行这个类的< clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行< clinit>()方法完毕。
7. 反射获取类的结构信息
各种API:
- Class类的方法
| 方法名 | 说明 |
|---|---|
| forName() | (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 |
| (2)为了产生Class引用,forName()立即就进行了初始化。 | |
| Object-getClass() | 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。 |
| getName() | 取全限定的类名(包括包名),即类的完整名字。 |
| getSimpleName() | 获取类名(不包括包名) |
| getCanonicalName() | 获取全限定的类名(包括包名) |
| isInterface() | 判断Class对象是否是表示一个接口 |
| getInterfaces() | 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。 |
| getSupercalss() | 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。 |
| newInstance() | 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。 |
| getFields() | 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。 |
| getDeclaredFields | 获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。 |
获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
| 方法返回值 | 方法名称 | 方法说明 |
|---|---|---|
| static Class<?> | forName(String className) | 返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
| Constructor | getConstructor(Class<?>... parameterTypes) | 返回指定参数类型、具有public访问权限的构造函数对象 |
| Constructor<?>[] | getConstructors() | 返回所有具有public访问权限的构造函数的Constructor对象数组 |
| Constructor | getDeclaredConstructor(Class<?>... parameterTypes) | 返回指定参数类型、所有声明的(包括private)构造函数对象 |
| Constructor<?>[] | getDeclaredConstructors() | 返回所有声明的(包括private)构造函数对象 |
| T | newInstance() | 调用无参构造器创建此 Class 对象所表示的类的一个新实例。 |
关于Constructor类本身一些常用方法如下(仅部分,其他可查API)
| 方法返回值 | 方法名称 | 方法说明 |
|---|---|---|
| Class | getDeclaringClass() | 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数) |
| Type[] | getGenericParameterTypes() | 按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。 |
| String | getName() | 以字符串形式返回此构造方法的名称。 |
| Class<?>[] | getParameterTypes() | 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型 |
| T | newInstance(Object... initargs) | 使用此 Constructor对象表示的构造函数来创建新实例 |
| String | toGenericString() | 返回描述此 Constructor 的字符串,其中包括类型参数。 |
同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:
| 方法返回值 | 方法名称 | 方法说明 |
|---|---|---|
| Field | getDeclaredField(String name) | 获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
| Field[] | getDeclaredFields() | 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 |
| Field | getField(String name) | 获取指定name名称、具有public修饰的字段,包含继承字段 |
| Field[] | getFields() | 获取修饰符为public的字段,包含继承字段 |
下面是Class类获取Method对象相关的方法:
| 方法返回值 | 方法名称 | 方法说明 |
|---|---|---|
| Method | getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
| Method[] | getDeclaredMethods() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
| Method | getMethod(String name, Class<?>... parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
| Method[] | getMethods() | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。
| 方法返回值 | 方法名称 | 方法说明 |
|---|---|---|
| Object | invoke(Object obj, Object... args) | 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 |
| Class<?> | getReturnType() | 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型 |
| Type | getGenericReturnType() | 返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。 |
| Class<?>[] | getParameterTypes() | 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组 |
| Type[] | getGenericParameterTypes() | 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型 |
| String | getName() | 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称 |
| boolean | isVarArgs() | 判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。 |
| String | toGenericString() | 返回描述此 Method 的字符串,包括类型参数。 |
8.反射一些实战
8.1反射构造
暴力破解:
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class aClass = Class.forName("反射.User");
Constructor[] constructors = aClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName());
}
Field declaredField = aClass.getDeclaredField("age");
System.out.println(declaredField);
Constructor constructor = aClass.getDeclaredConstructor(int.class, String.class);
constructor.setAccessible(true);//暴力破解
Object o = constructor.newInstance(21, "张三");
System.out.println(o.getClass());//运行时的类
Method age = aClass.getMethod("setAge", int.class);
age.invoke(o, 19);
Method age1 = aClass.getMethod("getAge");
Object invoke = age1.invoke(o);
System.out.println(invoke.getClass()+"----"+invoke);
}
}
class User{
private class inner{
private String name;
}
private int age;
public String name;
public User(){}
private User(int age,String name){
this.age=age;
this.name=name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}8.2 反射属性
Field declaredField1 = aClass.getDeclaredField("age");
Field declaredField2 = aClass.getDeclaredField("name");
System.out.println(declaredField1);
System.out.println(declaredField2);
declaredField1.setAccessible(true);
declaredField1.set(o,99);8.3 反射方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
Method setAge = aClass.getDeclaredMethod("setAge", int.class);
Object invoke = setAge.invoke(o, 23);
System.out.println(invoke);
Method setAge = aClass.getDeclaredMethod("setAge", int.class);
setAge.setAccessible(true);
setAge.invoke(o, 23);
Method getAge = aClass.getDeclaredMethod("getAge");
getAge.setAccessible(true);
System.out.println(getAge.invoke(o));
当类中有static时候:
private static String say (String mes){
System.out.println(mes);
return mes;
}可以传入null
Method say = aClass.getDeclaredMethod("say", String.class);
say.setAccessible(true);
Object invoke = say.invoke(null, "hello!");
System.out.println(invoke.getClass()+"--------内容:"+invoke);
总结:
反射遵循ocp设计模式(不修改源码,通过修改文件),所以反射运用于后面的框架当中很常见,反射的底层设计到JVM虚拟机,主要是类加载过程(各个阶段)
反射的各种API的使用和各种爆破增强对反射的理解。
如果想增强反射的理解,应该深入JVM原理再回来去理解。
