Java反射机制
1、反射是什么?
Java反射机制(Java Reflection)是Java语言中一种动态(运行时)访问、检测以及修改它本身的能力。主要作用是动态获取类完整的结构信息和调用对象的方法。
正射和反射的区别:
一般情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过 new 实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射。
而反射则是不知道要初始化的是什么类,无法使用 new 来实例化创建对象,主要通过 JDK 提供的反射 API 来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射。
举例:
1 | public class Student { |
2、反射获取 Class 的三种方式
在 Java 中,反射获取 Class 有三种方式,这三种方式各有各的优点。
以下是三种获取 Class 的方式:
- 通过
类名.class
来获取。 - 通过
Class.forName("类的全局路径")
来获取。 - 通过
new 类名().getClass()
来获取。
使用第一种方式获取 Class,JVM 会使用 ClassLoader 类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回 java.lang.Class 对象。
使用第二种方式获取 Class,类会被 JVM 加载到内存中,并且会进行类的静态初始化工作,返回 java.lang.Class 对象。
第三种获取 Class 的方式,它使用了 new 进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass 方法属于顶级 Object 类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的 java.lang.Class 对象。
3、反射的优缺点
优点:
在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
4、方法的反射
步骤思路:
1.获取该类的类类型
2.通过类类型获取类的方法(getMethods())
3.循环遍历所获取到的方法
4.通过这些方法的getReturnType()得到返回值类型的类类型,又通过该类类型得到返回值类型的名字
5.getName()得到方法的名称,getParameterTypes()获取这个方法里面的参数类型的类类型。
实例:
1 | public class ClassUtil { |
5、成员变量的反射
成员变量也是对象,是java.lang.reflect.Field类的对象,那么也就是说Field类封装了关于成员变量的操作。既然它封装了成员变量,我们又该如何获取这些成员变量呢?它有这么一个方法:
1 | public class ClassUtil { |
这里的getFields()方法获取的所有的public的成员变量的信息。和方法的反射那里public的成员变量,也有一个获取所有自己声明的成员变量的信息:Field[] fs = c.getDeclaredFields();
我们得到它之后,可以进行遍历(既然封装了Field的信息,那么我们就可以得到Field类型)
1 | for (Field field : fs) { |
6、Class类的动态加载类
动态加载是啥意思?怎么样动态加载一个类呢?
首先我们需要明白的是,动态加载和静态加载的概念以及区别。
我们一般所理解的,在编译的时候所加载的类是静态加载类;而运行的时候加载的类是动态加载类。
1 | Class A{ |
上面这一段代码,如果我们仅仅是在 IDEA 之中运行,是可以正常运行不发生错误的,而如果我们在 CMD 控制台运行的话,就会抛出问题。如下:
1 | A.java:7:错误:找不到符号 |
此时,因为B不存在,就应该是错误的。
但是,仔细想想,会发现B我们不一定使用,C我们也不一定使用。
现在我们来模拟一下B存在的情况。
也就是先编译B,然后再编译A。
但是此时还是有问题,C也是不存在的。
但是我们现在根本就不想用C,我们只想用B,那怎么办?
想要正常使用B,我们就不得不将C一起编译。而如果有ABCDEFGHIJKLMN个类,我们同样只想用B,那么我们是不是同样得全部编译他们呢?答案是肯定的。这样子肯定不是很好的。
以上这种说法就是所谓的静态加载类。
而解决这个问题的方法也很简单,就是改为动态加载类。即要使用的时候再创建。
我们新建一个类:
1 | Class All{ |
Class.forName(“类的全称”),它不仅仅表示了类的类类型,还表示了动态加载类。当我们javac A.ava的时候,它不会报任何错误,也就是说在编译的时候是没有错误的。只有当我们具体用某个类的时候,那个类不存在,它才会报错。
如果我们要加载B,那么就需要:B bt = (B) cl.newInstance();
加载C,就是:C ct = (C) cl.newInstance();
DEFG等等都是这样。
但是如果有一百各类,我们不可能写一百句这个,太冗余了。
我们可以写一个统一处理的方法,统一一个标准,只要满足这个标准的都可以使用这个方法。and s = (Stand) cl.newInstance();
例如B类:
1 | Class B implements Stand{ |
即满足了前面的标准。就可以通过这个统一的标准来运行。
如果以后想用某一个类,不需要重新编译,只需要实现这个标准的接口即可。只需要动态的加载新的东西就行了。
这就是动态加载类。