为了大家方便理解Java反射机制,建议先阅读《Java核心概念》
Class对象
当ClassLoader加载一个class文件到JVM的时候,会自动创建一个该类的Class对象,并且这个对象是唯一的,后续要创建这个类的任何实例,都会根据这个Class对象来创建。因此每当加载一个class文件的时候,都会创建一个与之对应的Class对象。
- 解析一个类的各个部分,形成一个对象。
外存中的类,加载到内存中,会形成该对象的Class类,例如:String类,加载到内存中,就是StringClass对象。
也就是说类是java.lang.Class类的实例对象,而Class是所有类的类。
对于普通的对象,一般都的创建方式:
1 | String s = new String(); |
- 既然类都是Class的对象,那么能否像普通对象一样创建呢,当看源码时,是这样写的:
1 | private Class(ClassLoader loader){ |
- 源码里构造器是私有的,只有JVM可以创建Class的对象,虽然我们不能new一个Class对象,但是可以从已有的类得到一个Class对象,共有三种方式,如下:
1 | // 类名.class 通过获取类的静态成员变量class得到(任何类都有一个隐含的静态成员变量class) |
- (注意: 这三种方式都是利用反射获取的都是同一个Class对象,这也叫做String的类类型,也就是描述何为类,一个类都有哪些东西,所以可以通过类类型知道一个类的属性和方法,并可以调用一个类的属性和方法,这就是反射的基础。)
反射
反射是指在程序的运行期间动态的去操作某个Class对象里面的成员
(包括类信息、属性信息、方法信息等元素)。它可以让Java这种静态语言具备一定的动态性。目前大部分的开源框架实现都是基于反射的机制实现。
JVM → 类加载 → class文件 → 创建 → Class对象 → 构建类的实例 → instance(实例);
重点在运行时动态的操作Class对象。
反射机制的利与弊
为何要用反射机制?直接new对象不ok了吗,这就涉及到了动态与静态的概念
- 静态编译:在编译时确定类型,绑定对象,即通过。
- 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有利于降低类之间的藕合。
优点:
- 可以实现动态创建对象和编译。比如,一个软件,不可能第一个版本就把它设计的很完美,当这个程序编译成功,发布后,当发现某些功能需要更新时,我们不可能要用户把旧版的卸载,再重新安装新的版本。
- 采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
一句话总结:运行期类型的判断,动态类加载,动态代理就使用了反射
缺点:
- 对性能有影响。反射相当于一系列解释操作,通知JVM要做的事情。性能比直接的java代码执行相同的操作要慢很多。
- 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
反射机制的相关操作
创建实例
1 | // 在反射操作之前的第一步,就是要先获取Class对象 |
动态操作属性
- 通过Class对象可以动态的获取和操作类中的属性,属性在JDK中有一个类来进行封装,就是Field,Field提供了一些常用的API方法让我们去访问和操作类中的属性
getField()
// 获取所有公开的属性字段(包括继承父类的公有属性)getDeclaredField()
// 获取本类所有(包括公有和私有,但是不包括父类的)的属性字段(注意:如果要访问和操作私有属性,必须调用setAccessible方法,打开访问开关)getFields()
// 获取所有公有的属性(包括继承自父类的公有属性)getDeclaredFields()
// 获取本类所有的属性(包括共有和私有的,但是不包括父类的)set()
// 给属性赋值,需要传入两个参数,第一个参数是当前类的一个实例,第二个参数是具体要赋予的值get()
// 获取属性的值,需要传入一个当前类的实例作为参数getName()
// 获取属性的名称getType()
// 获取属性的类型isAnnotationPresent()
// 判断该属性上是否定义了指定的注解,需要传入一个注解的Class对象作为参数getAnnotation()
// 获取当前属性上的注解对象,需要传入一个注解的Class对象作为参数
1 | public static void main(String[] args)throwsException{ |
动态操作方法
- 对于Class中的方法,API也提供了相应的类来进行封装,就是Method
getMethod()
// 获取指定的公共的方法(包括继承自父类公共的),需要传递两个参数,第一个参数是方法名称,第二个参数是一个可变参数,传递的是方法参数的类型getMethods()
// 获取所有的公共的方法(包括继承父类的公共方法)。getDeclaredMethod()
// 获取本类中指定的方法(包括私有和共有的,不包括父类的),需要传递两个参数,第一个参数是方法名称,第二个参数是一个可变参数,传递的是方法参数的类型。如果是私有方法,同样需要先打开访问开关(setAccessible(true))。getDeclaredMethods()
// 获取本地中所有的方法(包括私有和公共的,不包括父类)getName()
// 获取方法名称getReturnType()
// 获取方法的返回值类型getParameterTypes()
// 获取方法中所有的参数类型getParameterCount()
// 获取方法中参数的总个数getParameters()
// (JDK1.8新特性)获取方法中所有的参数信息,每一个参数信息都是一个Parameter类的对象。可以通过这个对象获取各个参数的类型以及名称(注意:如果要获取参数名,在编译的时候需要加上一个parameters参数,如:javac -parameters Xxx.java。或者是在开发环境中设置相应的编译选项)。invoke()
// 回调当前方法,需要传递两个参数,第一个是当前类的实例,第二个是一个可变参数,需要传入调用方法是所需的参数值。
1 | public static void main(String[] args)throwsException{ |
动态操作构造方法
- Constructor是在反射API中用于封装构造方法的一个类,因此通过这个类可以获取构造方法的一些信息,以及通过这个对象来实例化一个类的实例。
getConstructor()
// 获取无参并且公共的构造方法getDeclaredConstructor()
// 获取一个构造方法可以是私有的也可以是公共的,需要传入一个可变参数,就是构造方法的参数类型(注意:如果是私有的,必须先打开访问开关)newInstance()
// 通过构造方法创建实例,也需要传入一个可变参数,传入的是具体的值getConstructors()
// 获取所有公共的构造方法,返回的是一个Constructor数组getDeclaredConstructors()
// 获取所有的构造方法(包括私有和共有的),同样返回的是一个数组getParameters()
// 获取所有的参数对象,和Method一样getParameterTypes()
// 获取所有的参数类型,同Method一样
1 | public static void main(String[] args)throwsException{ |
Class中的一些API
- Class对象本身提供了很多的API方法用于获取和操作Class对象。
getPackage()
// 获取当前类所在的包,使用Package对象进行封装,可以从中获取包的信息,例如:包名getSimpleName()
// 获取当前类的简单类名(不包括包名)getName()
// 获取当前类的完整类名(包括包名)getSuperclass()
// 获取当前类的父类,返回的也是一个Class对象getInterfaces()
// 获取当前类所实现的所有接口,返回的是一个Class数组isAnnotationPresent()
// 判断当前类上是否定义了注解getAnnotation()
// 获取类上定义的注解
通过反射了解集合泛型的本质
- Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。
1 | public static void main(String[] args){ |