回顾反射
反射的概念
反射是动态语言的关键,是指程序可以访问、检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。Java中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码链接。反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但是反射的成本很高。
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时判断任意一个对象的成员变量和方法
- 生成动态代理
使用
获取运行时类的Class对象
Class对象,对应着一个运行时类本身充当了Class的一个实例。
第一种方式
Class<Person> clazz = Person.class;
第二种方式
Person p = new Person();
Class clazz = p.getClass();
第三种方式
String className = "com.noblel.Person";
Class clazz = Class.forName(className);
第四种方式
String className = "com.noblel.Person";
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz5 = classLoader.loadClass(className);
有了Class实例可以做什么?
-
创建一个类的实例
//以前的方式
Person p = new Person();
//有了反射还可以这样
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
//当然还可以获取指定构造器执行
Constructor cons = clazz.getDeclaredConstructor(String.class, int.class);
cons.setAccessible(true);
Person jack = (Person) cons.newInstance("jack", 20);
区别:都是创建对象,newInstance和getDeclaredConstructor的区别在于clazz.newInstance的执行只能是在类有空参的构造器,而且构造器的权限修饰符为public,而另外一种则可以是private。
- 获取对应的运行时类的完整的类的结构:属性、方法、构造器、包、父类、接口、泛型、注解、异常、内部类。。。
Class常用相关方法
- getFields():只能获取到运行时类中及其父类声明为public的属性
- getDeclaredFields():获取运行时本类本身声明的所有属性
Field常用相关方法
- getModifiers():获取每个属性的权限修饰符
- getType():获取变量类型
- getName():获取变量名
Method常用相关方法
- getName():获取方法名
- getAnnotation(Check.class):获取有Check注解的方法
- invoke(对象,参数...):让对象执行方法
Modifier常用相关方法
- toString(int mod)
- isStatic(int mod)
- isProtected(int mod)
-
调用对应的运行时类中指定的结构(某个指定的属性、方法、构造器)
Class clazz = Person.class;
Method show = clazz.getMethod("show");
Person p = (Person) clazz.newInstance();
Object invoke = show.invoke(p);
System.out.println(invoke);Method toString = clazz.getMethod("toString");
String invoke1 = (String) toString.invoke(p);
System.out.println(invoke1);//静态方法的调用
Method info = clazz.getMethod("info");
info.invoke(Person.class);
反射优缺点
反射机制的优点:
- 可以实现动态创建对象和编译
- 使代码更加灵活,更容易面向对象,动态代理等
反射机制的缺点: - 性能问题。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。
- 使用反射会模糊程序内部实际要发生的事情。我们希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题。反射代码比相应的直接代码更复杂。解决这些问题的最佳方案是保守地使用反射——仅在它可以真正增加灵活性的地方——记录其在目标类中的使用。
原理
反射是如何做到的呢?
类的加载过程:
类的加载分为五步:
装载,链接,初始化,使用,销毁
一. 装载:通过类的全限定名转换为二进制字节流,在jvm堆中生成代表这个Class的对象,作为方法区域的方法入口,并且在堆区创建描述这个类的java.lang.Class对象,用来封装数据,但是同一个类只能被类装载器装载一次
二. 链接:这里分为三步
- 验证:验证class字节流是否对jvm造成伤害,是否符合jvm的规范,是否适合当前jvm版本
- 准备:就是为类的static变量分配空间赋初始值,其中不包含其他实例成员的初始化
- 解析:将常量池内的符号引用替换成直接引用直到所有的符号引用都可以被运行程序使用(建立完整的对应关系)。
三. 初始化
static代码块,构造函数,代码块执行
四. 使用
对象的属性和方法调用操作
五.销毁
当没有任何引用指向Class对象后进行GC操作卸载类
能获取到Class对象就能获取所有相关信息了。
类加载