工欲善其事必先利其器,反射你真的掌握了吗?本文详细给你讲解,没时间看的建议收藏!!!
Posted 波波烤鸭
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了工欲善其事必先利其器,反射你真的掌握了吗?本文详细给你讲解,没时间看的建议收藏!!!相关的知识,希望对你有一定的参考价值。
本文继续来给大家巩固基础,详细的来给大家介绍下反射的原理。Java Reflection
探寻反射的本质
1. 反射快速入门
反向探知,在程序运行过程中动态的获取类的相关属性
这种动态获取类的内容以及动态调用对象的方法和获取属性的机制.就叫做JAVA的反射机制
/**
* 反射的基本应用
* @param args
*/
public static void main(String[] args) throws Exception {
// 1.通过普通的方式获取对象
Car car = new Car();
System.out.println(car);
// 2.通过反射的方式获取Car对象
Class<?> cls = Class.forName("com.bobo.reflection.domain.Car");
Object o = cls.newInstance();
System.out.println(o);
// 通过反射获取 属性和 方法
Field brandField = cls.getField("brand");
System.out.println(brandField.get(o));
}
2.反射机制原理
反射机制允许程序在执行期借助于Reflection API获取任何类的内部信息(比如成员变量,构造方法,成员方法等),并能操作对象的属性及方法,反射在设计模式和框架底层都会使用到。
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称为:反射
2.1 Java反射机制可以完成:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
2.2 反射相关的主要类:
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法
2.3 反射的优缺点:
优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点:使用反射基本是解释执行,对执行速度有影响
反射调用优化-关闭访问检查
- Method和Field、Constructor对象都有setAccessible()方法
- setAccessible方法的作用是启动和禁用访问安全检查的开关
- 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率,参数值为false则表示反射的对象执行访问检查
效果演示:
public static void main(String[] args) throws Exception{
fun1();
fun2();
fun3();
}
/**
* 普通方法执行
*/
public static void fun1(){
long start = System.currentTimeMillis();
Cat cat = new Cat();
for (int i = 0; i < 100000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统方法掉执行的时间:" + (end - start));
}
/**
* 反射方式调用
*/
public static void fun2() throws Exception{
long start = System.currentTimeMillis();
Class<?> aClass = Class.forName("com.bobo.util.reflection.domain.Cat");
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi");
for (int i = 0; i < 100000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射调用执行的时间:" + (end - start));
}
/**
* 反射方式调用
*/
public static void fun3() throws Exception{
long start = System.currentTimeMillis();
Class<?> aClass = Class.forName("com.bobo.util.reflection.domain.Cat");
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi");
hi.setAccessible(true); // 取消反射调用方法时 取消访问检查
for (int i = 0; i < 100000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("优化-反射调用执行的时间:" + (end - start));
}
输出结果
传统方法掉执行的时间:3
反射调用执行的时间:114
优化-反射调用执行的时间:80
3 Class类介绍
3.1 基本介绍
1、Class也是类,因此也继承Object类
2、Class类对象不是new出来的,而是系统创建的
// 传统对象获取
/**
* new Cat(); 对象的时候会直接接入 ClassLoader的 loadClass方法中
* public Class<?> loadClass(String name) throws ClassNotFoundException {
* return loadClass(name, false);
* }
*/
// Cat cat = new Cat();
// 通过反射方式获取对象
/**
* 在反射的情况下,仍然是通过ClassLoader类加载的Class对象
* public Class<?> loadClass(String name) throws ClassNotFoundException {
* return loadClass(name, false);
* }
*/
Class<?> aClass = Class.forName("com.bobo.util.reflection.domain.Cat");
Object o = aClass.newInstance();
3、对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
在上面的案例里面其实是可以验证的
Class<?> aClass1 = Class.forName("com.bobo.util.reflection.domain.Cat");
Class<?> aClass2 = Class.forName("com.bobo.util.reflection.domain.Cat");
System.out.println(aClass1.hashCode());
System.out.println(aClass2.hashCode());
结果输出
460141958
460141958
4、每个类的实例都会记得自己是由哪个Class实例所生成的
5、通过Class可以完整地得到一个类的完整结构,通过相关的API实现
6、Class对象是存储在堆中的
7、类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名、访问权限等)
3.2 Class类的常用方法
直接通过案例来说明
public static void main(String[] args) throws Exception{
String classAllName = "com.bobo.util.reflection.domain.Car";
// 1.获取Car类对应的Class对象
Class<?> aClass = Class.forName(classAllName);
// 2.输出aClass
System.out.println(aClass);// 显示aClass对象是哪个类的Class对象
System.out.println(aClass.getClass()); // 输出 运行类型 com.lang.Class
// 3.获取包名
System.out.println(aClass.getPackage().getName());
// 4.得到全类名
System.out.println(aClass.getName());
// 5.通过类对象创建实例
Car car = (Car) aClass.newInstance();
System.out.println(car);
// 6.通过反射获取属性
Field brand = aClass.getField("brand");
System.out.println(brand.get(car));
// 7.通过反射给属性设置值
brand.set(car,"奔驰");
System.out.println(brand.get(car));
// 8.获取所有属性的值
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field.get(car));
}
}
输出结果为:
class com.bobo.util.reflection.domain.Car
class java.lang.Class
com.bobo.util.reflection.domain
com.bobo.util.reflection.domain.Car
com.bobo.util.reflection.domain.Car@1b6d3586
BMW
奔驰
奔驰
400000.0
白色
3.3 Class类对象的获取方式
1.Class.forName()
使用的前提条件是已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName方法获取,可能抛出ClassNotFoundException。
Class<?> aClass1 = Class.forName("com.bobo.util.reflection.domain.Cat");
应用的场景:多用于配置文件,读取类全路径,加载类
2.具体类.class
如果已经知道了具体的类,可以通过类的class属性类获取,该方式最为安全可靠,程序性能最高
Class cls1 = Car.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象。
3.对象.getClass
已经获取了某个类的实例,调用该实例的getClass()方法获取Class对象,
Class cls = Car.getClass();
应用场景:通过创建好的对象,获取Class对象
4.其他方式
ClassLoader cl = 对象.getClass().getClassLoader();
Class cls = cl.loadClass("类的全类名");
5.基本数据类型
对于基本数据类型通过如下方式来获取对应的Class对象
Class cls = 基本数据类型.class;
Class cls = int.class;
6.包装类
基本数据类型对应的包装类可以通过.TYPE得到Class类对象
Class cls = Integer.TYPE;
3.4 有Class对象的类型
1、外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2、interface:接口
3、数组
4、enum:枚举
5、annotation:注解
6、基本数据类型
7、void
public static void main(String[] args) {
Class<String> cls1 = String.class;// 外部类
Class<List> cls2 = List.class;// 接口
Class<Integer[]> cls3 = Integer[].class;// 数组
Class<float[][]> cls4 = float[][].class; // 二维数组
Class<Resource> cls5 = Resource.class; // 注解
Class<Thread.State> cls6 = Thread.State.class; // 枚举
Class<Long> cls7 = long.class; // 基本数据类型
Class<Void> cls8 = void.class; // void 数据类型
Class<Class> cls9 = Class.class; // Class
System.out.println("cls1 = " + cls1);
System.out.println("cls2 = " + cls2);
System.out.println("cls3 = " + cls3);
System.out.println("cls4 = " + cls4);
System.out.println("cls5 = " + cls5);
System.out.println("cls6 = " + cls6);
System.out.println("cls7 = " + cls7);
System.out.println("cls8 = " + cls8);
System.out.println("cls9 = " + cls9);
}
输出结果
cls1 = class java.lang.String
cls2 = interface java.util.List
cls3 = class [Ljava.lang.Integer;
cls4 = class [[F
cls5 = interface javax.annotation.Resource
cls6 = class java.lang.Thread$State
cls7 = long
cls8 = void
cls9 = class java.lang.Class
4.类加载
4.1 类加载概述
反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
举例说明:
public static void main(String[] args) throws Exception{
Scanner in = new Scanner(System.in);
System.out.println("请输入一个数字:");
String key = in.next();
switch (key){
case "1":
// Person p = new Person(); // 静态加载 编译不通过
break;
case "2":
// 反射 --》 动态加载 要执行到这行代码才会加载
Class<?> aClass = Class.forName("com.bobo.util.reflection.domain.Person");
Object o = aClass.newInstance();
break;
default:
System.out.println("----");
}
}
类加载时机
- 当创建对象时(new)
- 当子类被加载时
- 调用类中的静态成员时
- 通过反射
类加载的流程图:
4.2 类加载的5个阶段
类加载阶段
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象
连接阶段-验证
- 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 包括:文件格式验证(是否以魔数 0xcafebaba开头)、元数据验证。字节码验证和符号引用验证
- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
在loadClass中会有SecurityManager中会进行一系列的验证
也可以查看下对应的二进制文件
连接阶段-准备
JVM会在该阶段对静态变量分配内存并初始化(对应数据类型的默认初始值,比如0,0L,null,false等)。这些变量所使用的的内存都将在方法区中进行分配。
// m1 是实例变量 在准备阶段不会分配内存
private int m1 = 20;
// m2 静态变量 分配内存 默认初始化 是0 而不是30
private static int m2 = 30;
// m3 是static final 是常量,他和静态变量不一样,
// 赋值后就不能修改了,所以准备阶段的默认初始化赋值就是40
private static final int m3 = 40;
连接阶段-解析
虚拟机将常量池中的符号引用替换为直接引用的过程
初始化initialization
到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行<clinit>() 方法的过程
<clinit>()方法是由编译器按语句在源文件中出现的先后顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
public class Test06 {
public static void main(String[] args) {
System.out.println("---");
// 1.加载B类。并生成B的Class对象
// 2.链接阶段 num = 0;
/**
* 3.初始化阶段
* 依次自动收集类中的所有**静态变量**的赋值动作和静态代码块中的语句,并进行合并
* clinit(){
* System.out.println("B 的静态代码块");
* num = 500;
* num = 100;
* }
* 合并 num = 100
*/
System.out.println(B.num);
}
}
class B{
static {
System.out.println("B 的静态代码块");
num = 500;
}
static int num = 100;
}
虚拟机会保证一个类的<clinit>()方法在多个线程环境中被正确的加锁,同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。
5.获取类结构信息
5.1 java.lang.Class类
方法名 | 作用 |
---|---|
getName | 获取全类名 |
getSimpleName | 获取简单类名 |
getFields | 获取所有public修饰的属性、包括本类以及父类的 |
getDeclaredFields | 获取本类中所有的属性 |
getMethods | 获取所有的public修饰的方法,包括本类以及父类的 |
getDeclaredMethod | 获取本类中所有方法 |
getConstructors | 获取所有public修饰的构造器,只有本类 |
getDeclaredConstructors | 获取本类中所有构造器 |
getPackage | 以Package形式返回 包信息 |
getSuperClass | 以Class形式返回父信息 |
getInterfaces | 以Class[]形式返回父接口信息 |
getAnnotations | 以Annotation[]形式返回注解信息 |
5.2 java.lang.reflect.Field类
getModifiers:以int形式返回修饰符 默认修饰符 0 public是1 private是2 protected是4 static是8 final是16
getType:以Class形式返回类型
getName:返回属性名
5.3 java.lang.reflect.Method类
getModifiers:以int形式返回修饰符 默认修饰符 0 public是1 private是2 protected是4 static是8 final是16
getName:返回方法名
getReturnType:以Class形式获取返回类型
getParmeterTypes:以Class[] 返回参数类型数组
5.4 java.lang.reflect.Constructor类
getModifiers:以int形式返回修饰符 默认修饰符 0 public是1 private是2 protected是4 static是8 final是16
getName:返回构造器名称
getParmeterTypes:以Class[] 返回参数类型数组
6.反射爆破操作
当我们需要通过反射获取对象,操作属性和方法的时候,如果我们操作的是私有属性和方法的时候是会抛异常的,这时我们可以通过爆破的方式来处理。
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.bobo.reflection.domain.Person");
// 创建实例对象
Constructor<?> c = cls.getDeclaredConstructor();
// 放开对访问权限的修饰
c.setAccessible(true);
Object o = c.newInstance();
// 修改私有属性
Field age = cls.getDeclaredField("age");
age.setAccessible(true);
age.set(o,22);
// 方法调用
Method fun1 = cls.getDeclaredMethod("fun1"<以上是关于工欲善其事必先利其器,反射你真的掌握了吗?本文详细给你讲解,没时间看的建议收藏!!!的主要内容,如果未能解决你的问题,请参考以下文章