目录
一,反射机制
1.反射机制概念
在运行状态中,对于任意一个类,能够知道其所有结构信息(如:成员属性,方法,构造器等),对于任意一个对象,都能够调用其任一个方法及属性。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
2.反射机制原理
首先说一下Java程序在计算机中的三个阶段:第一个阶段是编译阶段:这个阶段.java文件被javac编译成.class字节码文件,第二个阶段是类加载阶段:这个阶段.class文件中的信息(成员方法,成员属性,构造器)被类加载器加载为Class类对象放到堆中,第三个阶段是运行阶段:这个阶段便是我们可以操作的阶段,可以创建对象并调用对象方法。
而反射机制便是在运行阶段,通过Class类对象,获取其结构信息,创建相应对象,调用方法,变量,构造器。
3..反射机制功能
Java反射机制可以完成:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类的成员变量和方法
- 在运行时调用任一个对象的成员变量和方法
- 生成动态代理
4..反射机制相关的类
上面说过,在Java的反射机制中,成员属性,方法,构造器等均可被视为对象,在调用时通过相关对象进行调用。反射机制中相关的类有:
类名 | 解释 |
Class | 代表一个类(类名就叫Class),Class对象表示某个类加载后在堆中的对象 |
Method | 代表类的方法,Method对象表示某个类的方法 |
Field | 代表类的成员属性,Field对象表示某个类的成员属性 |
Constructor | 代表类的构造方法,Constructor对象表示构造器 |
二,Class类
Class类是Java反射机制中一个非常重要的类,获取类结构信息,实例化对象,调用方法等都要用过Class类来完成。
1.Class类的特点
- Class也是类,继承Object类;
- Class类对象不是new出来的,而是系统创建的,也就是上文说的在类加载过程由类加载器创建,Class类对象存放在堆中;
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次,因此对于属于同一个类的两个对象,它们的Class类对象是相同的;
- 通过Class对象可以完整地得到一个类的完整结构,通过一系列的api;
- 类的.class字节码文件的二进制数据是放在方法区的,因为二进制数据不便于操作,便有了在堆中存放的Class类对象;
2. 拥有Class对象的类型
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型(基本数据类型的Class类对象与包装器类是同一个)
- void
3.获取Class对象的方式
1.通过Class的静态方法forName:
String classPath = "Text_Reflection.Cat";
Class cls1 = Class.forName(classPath);
System.out.println(cls1);
2.通过类名.class,多用于参数传递:
Class cls2 = Cat.class;
System.out.println(cls2);
3.有对象实例时,通过getClass获取:
Cat cat = new Cat();
Class cls3 = cat.getClass();
System.out.println(cls3);
4.通过类加载器获取:
String className = "Text_Reflection.Cat";
ClassLoader classLoader = cat.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(className);
System.out.println(cls4);
三,类加载
1.静态加载和动态加载
反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载。
静态加载:编译时加载相关的类,如果没有则报错,依赖性太强,因为有些类在运行时并不会被用到。
动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不会报错,降低了依赖性。
类静态加载的时机有:创建对象(new),当子类加载父类也加载时,调用类中的静态成员时。
类动态加载时机:通过反射
2.类加载的各个阶段
(1)加载
在这个阶段,计算机会将类的.class文件读入内存,并为之创建一个Class类对象,此过程由类加载器完成。
(2)连接阶段-验证
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全,包括:文件格式验证(是否以魔数 oxcafebabe开头),元数据验证,字节码验证和符号引用验证。
(3)连接阶段-准备
JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值,如0, 0L, null, false等)。这些变量所使用的内存都将在方法区中进行分配。(static final类型的变量会直接初始化为设置的值,因为static final类型的变量只会初始化一次)。
(4)连接阶段-解析
JVM将常量池内的符号引用替换为直接引用的过程。
(5)初始化
到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段执行<clinit>()方法,<clinit>()方法是由编辑器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并。虚拟机保证一个类的<clinit>()方法在多线程环境中被正确地加锁,同步。
四,通过反射获取类的结构信息
1.Class类
(1)getName:获取全类名
//getName 获取全类名
System.out.println(cls.getName());
(2)getSimpleName:获取简单类名
//getsimpleName 获取简单类名
System.out.println(cls.getSimpleName());
(3)getFields:获取所有public修饰的成员属性,包含本类及父类的
//getFields 获取所有public属性,包括父类的
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println("本类及父类属性:" + field.getName());
}
(4)getDeclaredFields:获取本类中所有的属性,不包括父类
//getDeclaredFields:获取本类所有属性 没有父类的
Field[] fields1 = cls.getDeclaredFields();
for (Field field : fields1) {
System.out.println("所有属性:" + field.getName());
}
(5)getMethods:获取所有public修饰的方法,包含本类及父类的
//getMethods 获取所有public方法
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println("public方法:" + method.getName());
}
(6)getDeclaredMethods:获取本类中所有的方法,不包括父类
//getDeclaredMethods 获取所有方法 自己的,不包括父类和超类
Method[] methods1 = cls.getDeclaredMethods();
for (Method method : methods1) {
System.out.println("all方法" + method.getName());
}
(7)getConstructors:获取所有public修饰的构造器,不包括父类,因为子类不会继承父类的构造器
//getConstructors 获取所有public的构造器 不能拿到父类的,因为子类不会继承父类的构造器
Constructor[] constructors = cls.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("public构造器:" + constructor.getName());
}
(8)getDeclaredConstructors:获取本类中所有构造器,不包括父类
//getDeclaredConstructor 获取所有构造器
Constructor[] constructors1 = cls.getDeclaredConstructors();
for (Constructor constructor : constructors1) {
System.out.println("所有构造器" + constructor.getName());
}
(9)getPackage:以Package形式返回包信息
//getPackage 获取包名
System.out.println(cls.getPackage().getName());
(10)getSuperClass:获取父类的Class对象
//getSuperClass 获取父类的Class对象
System.out.println(cls.getSuperclass().getName());
(11)getInterfaces:以Class[]形式返回接口信息
//getInterfaces 以Class[]形式返回接口信息
Class[] classInterfaces = cls.getInterfaces();
for (Class classInterface : classInterfaces) {
System.out.println(classInterface);
}
(12)getAnnotations:以Annotation[]形式返回注解信息
//getAnnotations 以Annotation[]形式返回注解信息
Annotation[] Annotations = cls.getAnnotations();
2.Field类
(1)getModfiers:以int形式返回修饰符(默认修饰符default是0, public是1, private是2,protected是4, static是8, final是16,若是多个修饰符则相加,例如public static返回 1 + 8 = 9)。
(2)getType:以Class形式返回类型
(3)getName:返回类型名
public static void api2() throws ClassNotFoundException{
Class cls = Class.forName("Text_Reflection.Son");
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("属性:" + field.getName() + "的修饰符类型为" + field.getModifiers() + " 该属性的数据类型为" + field.getType());
}
}
3.Method类
(1)getModifiers:以int形式返回修饰符
(2)getReturnType:以Class形式获取方法返回类型
(3)getName:返回方法名
(4)getParameterTypes:以Class[]形式返回参数类型数组
public static void api2() throws ClassNotFoundException{
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println("方法" + method.getName() +"的返回类型为" + method.getReturnType());
Class[] parameterTypes = method.getParameterTypes();
for (Class parameter : parameterTypes) {
System.out.println("方法" + method.getName() + "参数类型为" + parameter + " " + parameter.hashCode());
}
}
}
4.Constructor类
(1)getModifiers:以int形式返回修饰符
(2)getName:返回构造器名(全类名)
(3)getParaMeterTypes:以Class[]返回参数类型数组
public static void api2() throws ClassNotFoundException{
Constructor[] constructors = cls.getDeclaredConstructors();
for (Constructor constructor : constructors) {
Class[] constructorType = constructor.getParameterTypes();
for (Class aClass : constructorType) {
System.out.println("构造器" + constructor.getName() + "参数类型为" + aClass);
}
}
}
五,通过反射创建类的实例
假设如下一个user类,有三种构造器:
class User{
public User(){}
public User(String name){}
private User(String name, int num){}
}
对于三种构造器,创建实例的方法为:
1.public无参构造:newInstance()
//1.public无参构造
Class userClass = Class.forName("Text_Reflection.User");
Object user1 = userClass.newInstance();
2.public有参构造:通过构造器Constructor,需要传入参数的Class对象
//2.public有参构造
Class userClass = Class.forName("Text_Reflection.User");
Constructor userConstructor = userClass.getConstructor(String.class);
userConstructor.newInstance("吴彦祖");
3.private有参构造:通过构造器Constructor及反射暴破,直接使用private的构造器会报错,需要使用setAccessible将安全检查取消,使得可以访问private构造器,这便是反射暴破
//3.private有参构造
Class userClass = Class.forName("Text_Reflection.User");
Constructor userConstructor1 = userClass.getDeclaredConstructor(String.class, int.class);
userConstructor1.setAccessible(true); //暴破(暴力破解),取消安全检查,使得可以访问private构造器,破环了封装性
Object user2 = userConstructor1.newInstance("吴彦祖" , 114514);
System.out.println(user2);
六,通过反射调用属性及方法
假设如下一个user类:
class User{
private int num;
private static String name;
private int fun(int num){
return 2333;
}
}
1.通过反射调用属性
对于成员属性,我们可以用set方法设置属性值,get方法获取属性值,这两种方法都由属性Class对象调用,调用时需传入对应类的对象,调用私有属性需要用到反射暴破:
Class userClass = Class.forName("Text_Reflection.User");
Object user1 = userClass.newInstance();
Field num = userClass.getDeclaredField("num");
num.setAccessible(true); //调用私有属性,反射暴破
num.set(user1, 114514); //更改num的值
System.out.println(num.get(user1)); //返回num的值
对于静态成员属性,因为静态属性属于类而不属于对象,所以调用set,get方法时可以传入任意对象:
Class userClass = Class.forName("Text_Reflection.User");
Object user1 = userClass.newInstance();
Field name = userClass.getDeclaredField("name");
name.setAccessible(true);
name.set(null, "jjb"); //静态属性与对象无关,任何对象都可访问
System.out.println(name.get(null));
2.通过反射调用方法
对于成员方法,使用invoke方法调用,同样是由方法的Class对象调用,调用时需要传入对应类的对象:
Class userClass = Class.forName("Text_Reflection.User");
Object user = userClass.newInstance();
Method fun = userClass.getDeclaredMethod("fun", int.class);
fun.setAccessible(true);
Object returnValue = fun.invoke(user,1000);
System.out.println(returnValue);
七,反射的优化
反射基本是解释执行,所以对执行速度有很大影响,例如下面分别使用传统方式和反射方式调用同一方法1e8次:
public class testReflectionTime {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
normalFun();
reflectionFun();
reflectionOptimizeFun();
}
public static void normalFun(){
Cat cat = new Cat();
long start = System.currentTimeMillis();
for(int i = 1; i <= 1000000000; i++){
cat.cry();
}
long end = System.currentTimeMillis();
System.out.println("传统方法耗时:" + (end - start) + "秒;");
}
public static void reflectionFun() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("Text_Reflection.Cat");
Object object = cls.newInstance();
Method method = cls.getMethod("cry");
long start = System.currentTimeMillis();
for(int i = 1; i <= 1000000000; i++){
method.invoke(object);
}
long end = System.currentTimeMillis();
System.out.println("反射方法耗时:" + (end - start) + "秒;");
}
}
可以看到反射方法耗时远远多于传统方法:
我们可以通过关闭访问检查的方法来对反射进行优化,其实就是前面的反射暴破set-Accessible,setAccessible作用是启动和禁用访问安全检查的开关,参数为true表示反射的对象在使用时取消访问检查,提高反射效率,参数值为false则表示反射的对象执行访问检查。
public static void reflectionFun() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("Text_Reflection.Cat");
Object object = cls.newInstance();
Method method = cls.getMethod("cry");
method.setAccessible(true); //在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for(int i = 1; i <= 1000000000; i++){
method.invoke(object);
}
long end = System.currentTimeMillis();
System.out.println("反射优化方法耗时:" + (end - start) + "秒;");
}
优化效果如下,可以看到有效果,但不多: