3233 字
16 分钟
反射
反射入门
为什么需要反射?
需要根据外部文件配置,在不修改源码的情况下来控制程序,传统方法是无法实现的。
示例
//使用properties读取配置文件
//3. 使用反射机制解决//(1) 加载类, 返回 Class 类型的对象 clsClass cls = Class.forName(classfullpath);//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例Object o = cls.newInstance();System.out.println("o 的运行类型=" + o.getClass()); //运行类型//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象// 即:在反射中,可以把方法视为对象(万物皆对象)Method method1 = cls.getMethod(methodName);//(4) 通过 method1 调用方法: 即通过方法对象来实现调用方法System.out.println("=============================");method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)//java.lang.reflect.Field: 代表类的成员变量, Field 对象表示某个类的成员变量//得到 name 字段//getField 不能得到私有的属性Field nameField = cls.getField("age"); //System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象)//java.lang.reflect.Constructor: 代表类的构造方法, Constructor 对象表示构造器Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器System.out.println(constructor);//Cat()Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是 String 类的Class 对象System.out.println(constructor2);//Cat(String name)
反射机制
介绍
- 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性及反复噶,反射在设计模式和框架底层中都很重要
- 加载完类之后,在堆中就产生了一个Class对象(一个类只有一个class对象),该对象包含了类的所有信息,包括类名,包名,父类,接口,方法,成员变量等。通过这个对象得到类的结构。
- 这个class对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为反射
Java反射机制原理示意图
反射机制的作用
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
反射机制的优点和缺点
优点:可以动态的创建和使用对象,使用灵活,没有反射机制,框架技术就失去底层支撑
缺点:使用反射基本是解释执行,对执行速度有影响
反射调用优化-关闭访问检查
- Method和Filed,Constructor对象都有setAccessible方法,
- setAccessible作用时启动和禁用访问安全检查的开关
- 参数为true表示反射的对象在使用时取消安全检查,提高反射的效率,参数值为false表示开启安全检查,默认开启安全检查
class类
基本介绍
- Class对象不是new出来的,而是系统创建的
- 对于某个类的Class对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个Class实例所生成
- Class对象时存放在堆中的
- 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
- 类的字节码二进制数据是存放在方法去的,有的地称为类的元数据(包括方法代码,变量名,方法名,访问权限等)
Class类常用方法
static Class forName(String className):根据类名获取Class对象
Object newInstance():根据Class对象创建类的实例
getName():获取类的全名
Class[] getInterfaces():获取类的接口
Class getSuperclass():获取类的父类
ClassLoader getClassLoader():获取类的类加载器
Method getMethod(String name, Class… parameterTypes):根据方法名和参数类型获取方法对象
Field getField(String name):根据成员变量名获取成员变量对象
Field[] getDeclaredFields():返回Field对象的一个数组
Constructor getConstructor(Class… parameterTypes):根据参数类型获取构造器对象
获取Class对象
- 通过类名获取Class对象
- Class cls = Class.forName(“com.hspedu.Cat”);
- 已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能好
- Class cls2 = Cat.class;
- 已知某个类的实例,调用该实例的getClass()方法获取Class对象
- Class clazz = 对象.getClass();
- 其他方式
- ClassLoader cl = 对象.getClass().getClassLoader();
- Class cls3 = cl.loadClass(“类的全类名”);
- 基本数据类型
- Class cls5 = int.class;
- 包装类
- Class cls4 = Integer.TYPE;
哪些类型有Class对象
- 所有类
- 接口
- 数组
- 基本数据类型
- 注解
- 枚举
- void
类加载
基本说明
反射机制是java实现动态语言的关键,通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载相关的类,如果运行时不用该类,即使不存在该类,也不报错,依赖性小,灵活性强
类加载时机
- 当创建对象时加载//静态加载
- 当子类被加载的时候,父类也加载 //静态加载
- 调用类中的静态成员时 //静态加载
- 通过反射 // 动态加载
类加载过程图
类加载各阶段完成任务
加载阶段
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象。
连接阶段
验证阶段
- 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 包括:文件格式验证、元数据验证、字节码验证、符号引用验证
- 可以考虑使用-Xverify
参数关闭该阶段的验证,缩短虚拟机类加载的时间
准备阶段
- JVM会在该阶段堆静态变量分配内存,并设置默认初始值(对应数据类型的默认初始值,如0,0L,null,false等)。这些变量所使用的内存都将在方方法区中进行分配
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是 20//3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30public int n1 = 10;public static int n2 = 20;public static final int n3 = 30;
解析阶段
- 虚拟机将常量池内的符号引用替换为直接引用,符号引用就是一组符号来描述所引用的目标,直接引用就是直接指向目标的指针、偏移量或相对偏移量。(因为之前还没分配内存,只能拿符号代表一下)
初始化阶段
- 到初始化阶段,才真正开始执行类中定义的Java程序代按摩,此阶段是执行
()方法的过程 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的静态变量的赋值动作和静态代码块中的语句,并进行合并 - 虚拟机会保证一个类的
()方法在多线程环境中被正确地加锁和同步,确保在多线程环境中,该方法只被执行一次(synchronized关键字)
反射获取类的结构信息
第一组:java.lang.Class类中的方法
- getName:获取全类名
- getSimpleName:获取简单类名
- getFields:获取所有public修饰的属性,包含本类以及父类的
- getDeclaredFields:获取本类中所有属性
- getMethods:获取所有public修饰的方法,包含本类以及父类的
- getDeclaredMethods:获取本类中所有方法
- getConstructors:获取本类所有public修饰的构造器
- getDeclaredConstructors:获取本类中所有构造器
- getPackage以Package形式返回包信息
- getSuperClass:以Class形式返回父类信息
- getInterfaces以class[]形式返回接口信息
- getAnnotations:以Annotation[]形式返回注解信息
第二组:java.lang.reflect.Field类中的方法
- getName:获取属性名
- getType:获取属性类型
- getModifiers:获取属性修饰符
第三组:java.lang.reflect.Method类中的方法
- getName:获取方法名
- getReturnType:获取返回类型
- getParameterTypes:获取参数类型
- getModifiers:获取方法修饰符
第四组 :java.lang.reflect.Constructor类中的方法
- getParameterTypes:获取构造器参数类型
- getModifiers:获取构造器修饰符
- getname:获取构造器名(全类名)
通过反射创建对象
- 方式一:调用类中的public修饰的无参构造器
- 方式二:调用类中的指定构造器
- class类相关方法
- newInstance:调用类中的无参构造器,获取对应类的对象
- getConstructor(Class…clazz):根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor(Class…clazz):根据参数列表,获取对应的所有构造器对象
- Constructor类相关方法
- setAccessible:暴力破解,访问私有构造器/方法/属性
- newlnstance(Object…obj):调用构造器
示例
//1. 先获取到 User 类的 Class 对象Class<?> userClass = Class.forName("com.hspedu.reflection.User");//2. 通过 public 的无参构造器创建实例Object o = userClass.newInstance();System.out.println(o);//3. 通过 public 的有参构造器创建实例/*constructor 对象就是public User(String name) {//public 的有参构造器this.name = name;}*///3.1 先得到对应构造器Constructor<?> constructor = userClass.getConstructor(String.class);//3.2 创建实例,并传入实参Object hsp = constructor.newInstance("hsp");System.out.println("hsp=" + hsp);//4. 通过非 public 的有参构造器创建实例//4.1 得到 private 的构造器对象Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);//4.2 创建实例//暴破【暴力破解】 , 使用反射可以访问 private 构造器/方法/属性, 反射面前,都是纸老虎constructor1.setAccessible(true);Object user2 = constructor1.newInstance(100, "张三丰");System.out.println("user2=" + user2);
class User { //User 类private int age = 10;private String name = "韩顺平教育";public User() {//无参 public}public User(String name) {//public 的有参构造器this.name = name;}private User(int age, String name) {//private 有参构造器this.age = age;this.name = name;}public String toString() {return "User [age=" + age + ", name=" + name + "]";}}
通过反射访问类中的成员
- 根据属性名获取Field对象
Fieldf=clazz对象.getDeclaredField(属性名)
- 暴破:
f.setAccessible(true);//f是Field
- 访问
- f.set(o,值):// o表示对象
- f.get(o)://o表示对象
- 注意:如果是静态属性,则set和get中的参数o,可以写成null
示例
public class ReflecAccessProperty { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { //1. 得到 Student 类对应的 Class 对象 Class<?> stuClass = Class.forName("com.hspedu.reflection.Student"); //2. 创建对象 Object o = stuClass.newInstance();//o 的运行类型就是 Student System.out.println(o.getClass());//Student //3. 使用反射得到 age 属性对象 Field age = stuClass.getField("age"); age.set(o, 88);//通过反射来操作属性 System.out.println(o);// System.out.println(age.get(o));//返回 age 属性的值 //4. 使用反射操作 name 属性 Field name = stuClass.getDeclaredField("name"); //对 name 进行暴破, 可以操作 private 属性 name.setAccessible(true); //name.set(o, "老韩"); name.set(null, "老韩~");//因为 name 是 static 属性,因此 o 也可以写出 null System.out.println(o); System.out.println(name.get(o)); //获取属性值 System.out.println(name.get(null));//获取属性值, 要求 name 是 static }}class Student {//类 public int age; private static String name; public Student() {//构造器 } public String toString() { return "Student [age=" + age + ", name=" + name + "]"; }}
通过反射调用类中的方法
- 根据方法名和参数列表获取Method方法对象:
Method m=clazz.getDeclaredMethod(方法名,XX.class)://得到本类的所有方法
- 获取对象:
Object o=clazz.newlnstance();
- 暴破:
m.setAccessible(true);
- 访问:
Object returnValue=m.invoke(o,实参列表);//o就是对象
- 注意:如果是静态方法,则invoke的参数o,可以写成null!
示例
public class ReflecAccessMethod { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//1. 得到 Boss 类对应的 Class 对象 Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss"); //2. 创建对象 Object o = bossCls.newInstance(); //3. 调用 public 的 hi 方法 //Method hi = bossCls.getMethod("hi", String.class);//OK //3.1 得到 hi 方法对象 Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK //3.2 调用 hi.invoke(o, "韩顺平教育~"); //4. 调用 private static 方法 //4.1 得到 say 方法对象 Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class); //4.2 因为 say 方法是 private, 所以需要暴破,原理和前面讲的构造器和属性一样 say.setAccessible(true); System.out.println(say.invoke(o, 100, "张三", '男')); //4.3 因为 say 方法是 static 的,还可以这样调用 ,可以传入 null System.out.println(say.invoke(null, 200, "李四", '女')); //5. 在反射中,如果方法有返回值,统一返回 Object , 但是他运行类型和方法定义的返回类型一致 Object reVal = say.invoke(null, 300, "王五", '男'); System.out.println("reVal 的运行类型=" + reVal.getClass());//String
//在演示一个返回的案例 Method m1 = bossCls.getDeclaredMethod("m1"); Object reVal2 = m1.invoke(o); System.out.println("reVal2 的运行类型=" + reVal2.getClass());//Monster }}class Monster {}class Boss {//类 public int age; private static String name; public Boss() {//构造器 } public Monster m1() { return new Monster(); } private static String say(int n, String s, char c) {//静态方法 return n + " " + s + " " + c; } public void hi(String s) {//普通 public 方法
System.out.println("hi " + s); }}
评论