18.反射
反射机制
反射机制允许程序在执行时借助于Reflection API 取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性及方法。
加载完类之后,在队中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了雷类的完整结构信息。通过这个对象得到类的结构。
类的反射原理图解
反射机制作用
- 在运行时判断一个对象所属的类
- 在运行时构造任意一个对象
- 在运行时得到任意一个对象所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
反射相关的主要类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Feild:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
实例:
//(1) 加载类, 返回 Class 类型的对象 cls
Class 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()
反射优点和缺点
- 优点:可以动态的创建和使用对象,使用灵活,框架底层核心
- 缺点:使用反射基本是解释执行,对执行速度有影响
反射调用优化:-- 关闭访问检查
Method、Field和Constructor对象都有setAccessible()方法,这个方法的作用是开启和禁用访问安全检查的开关,参数值设置为true,表示反射的对象在使用时取消访问检查,提高反射的效率。
Class类
Class对象 -->即类型为Class的对象,类加载到内存会创建一个对应的Class对象
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的
- 对于每个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class对象可以完整的得到一个类的完整结构,通过一系列API
- Class类对象是存放在堆中的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括变量名、方法名、访问权限等)
有Class类的对象
- 外部类、内部类
- 接口
- 数组
- 枚举
- 注解
- 基本数据类型
- void
Class类常用方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J6rX0Vjs-1636426526967)(E:\java\note\image\43.png)]
String str = "com.Cat";
//获取到Class类对象,?表示不确定的java类型
Class<?> class1 = Class.forName(str);
System.out.println(class1);//显示该class1对象是哪个类的Class对象
System.out.println(class1.getClass());//运行类型
System.out.println(class1.getPackage().getName());//获取包名
System.out.println(class1.getName());
Object obj = class1.newInstance();//通过反射创建对象
Field field = class1.getField("name");//通过反射获取属性
field.set(obj,"小花");//通过字段对象 赋值
Object obj2 = field.get(obj);//获取值
System.out.println(obj2);//输出
获取Class对象
- Class.forName()获取:多用于配置文件,读取全路径,加载类
// 1. Class.forName
Class<?> carClass1 = Class.forName("com.lsgstudy.Car");
System.out.println(carClass1); // class com.lsgstudy.Car
- 类名.class获取:多用于参数传递,通过反射得到对应构造器对象
// 2.类名.class
Class<Car> carClass2 = Car.class;
System.out.println(carClass2); // class com.lsgstudy.Car
- 对象名.getClass获取
// 3.对象名.getClass
Car car = new Car();
Class<? extends Car> carClass3 = car.getClass();
System.out.println(carClass3); // class com.lsgstudy.Car
- 通过类加载器获取
// 4.通过类加载器获取类的Class对象
// 获取类加载器
ClassLoader classLoader = car.getClass().getClassLoader();
// 通过类加载器获取到Class对象
Class<?> carClass4 = classLoader.loadClass("com.lsgstudy.Car");
System.out.println(carClass4); // class com.lsgstudy.Car
- 基本数据类型获取Class对象
// 5.获取基本数据类型的Class对象
Class<Integer> integerClass = int.class;
System.out.println(integerClass); // int
- 基本数据类型包装类获取Class对象
// 6.获取包装类的Class对象
Class<Integer> type = Integer.TYPE;
System.out.println(type); // int
- 其他:包、父类、接口、注解
Class<?> personCls = Class.forName("com.lsgstudy.reflection.class_.Person");
// 获取Class对象对应的类的包信息
System.out.println("=========获取Class对象对应的类的包信息=========");
Package personPackage = personCls.getPackage();
System.out.println(personPackage.getName());
// 获取Class对象对应的类的父类的Class对象
System.out.println("=======获取Class对象对应的类的父类的Class对象===========");
Class<?> superclass = personCls.getSuperclass();
System.out.println(superclass.getName());
// 获取Class对象对应的类的接口的Class对象
System.out.println("========获取Class对象对应的类的接口的Class对象==========");
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface.getName());
}
// 获取Class对象对应的类的注解的Class对象
System.out.println("===========获取Class对象对应的类的注解的Class对象============");
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
注意:其实int和Integer得到的Class对象是同一个,因为在运行时会自动装箱和拆箱
类加载
- 静态加载:编译时加载相关的类,如果没有就报错,依赖性太强
- 动态加载:运行时加载相关的类,如果运行时没用到该类,则不报错,降低了依赖性
类加载时机:
- 创建对象时(new)
- 子类被加载时
- 调用类的静态成员时
- 通过反射----动态加载
类加载过程
下面图片展示了Java程序从源码到运行的过程
源码到运行
类加载详细过程
加载:JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
连接:分为三个阶段
- 验证:目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括文件格式验证(是否以魔数 oxcafebebe开头)、元数据验证、字节码验证和符号引用验证。可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
- 准备:JVM会在该阶段对静态变量,分配内存并初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法去中进行分配
class A {
// 准备阶段不同类型的属性处理方式
// n1 是实例属性,不是静态变量,因此准备阶段,不会分配内存
// n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是20
// n3 是static final 是常量,他和静态变量不一样,因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
- 解析:虚拟机将常量池内的符号引用替换为直接引用的过程
初始化:到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程。
()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
虚拟机会保证一个类的()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2LYli6UE-1636426526974)(E:\java\note\image\44.png)]
通过反射获取类的结构信息
- getName:获取全类名
- getSimpleName:获取简单类名
- getFields:获取所有的public修饰的属性,包含本类以及父类的
- getDeclaredFields:获取本类中所有属性
- getMethods:获取所有public修饰的方法,包含本类以及父类
- getDeclaredMethods:获取本类所有的方法
- getConstructors:获取所有public修饰的构造器,包含本类
- getDeclaredConstructors:获取本类中所有的构造器
- getPackage:以Package形式返回 包信息
- getSuperClass:以Class形式返回父类信息
- getInterfaces:以Class[]形式返回接口信息
- getAnnotations:以Annotations[]形式返回注解信息
属性:Field
获取属性对象
- 获取public属性(包括父类的)
Field name = personClass.getField("name");
- 获取所有属性(不包括父类的)
Field age = personClass.getDeclaredField("age");
常用方法
- set:修改属性
// set(对象名,修改内容)
name.set(o,"李华");
- get:获取属性的值
// get(对象名)
// 返回值是Object类型,运行类型是实际的类型,可以向下转型
Object personName = name.get(o);
- getModifiers:获取修饰符值
// 说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
// public(1) + static(8) = 9
int modifiers = name.getModifiers();
- getType:获取数据类型的Class对象
Class<?> type = name.getType();
- getName:获取属性名
String fieldName = name.getName();
注意:
- 如果属性是非public的,赋值和获取值操作需要先关闭访问检查
- 如果属性是static的,需要传入对象名的方法可以传入null
代码示例
public static void main(String[] args) throws Exception {
// 获取Person类的Class对象
Class<?> personClass = Class.forName("com.lsgstudy.reflection.Person");
// 创建实例
Object o = personClass.newInstance();
// public属性
// 获取属性对象
Field name = personClass.getField("name");
// 设置属性
name.set(o,"李华");
System.out.println(o);
String a = "hhh";
// private static属性
// 获取属性对象
// 因为是private的属性,所以用getDeclaredField
Field age = personClass.getDeclaredField("age");
// 私有属性,想要赋值操作需要先关闭访问检查
age.setAccessible(true);
// static类型,不需要传入这个类的对象
age.set(a,5);
System.out.println(age.get(null));
}
方法:Method
获取方法对象
- 获取public方法(包括父类的)
// getMethod(方法名,参数数据类型的Class对象)
Method m1 = dogClass.getMethod("m1", int.class);
- 获取所有方法(不包括父类的)
// getDeclaredMethod(方法名,参数数据类型的Class对象)
Method m2 = dogClass.getDeclaredMethod("m2", String.class);
常用方法
- invoke:执行方法
// invoke(对象名,方法参数(可变参数))
// 如果方法有返回值,返回值统一是Object,可以向下转型
Object invoke = m1.invoke(o, 5);
- getName:获取方法名
String name = m1.getName();
- getModifiers:获取修饰符值
// 说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
// public(1) + static(8) = 9
int modifiers = m1.getModifiers();
- getParameterTypes:获取参数数据类型的Class对象
Class<?>[] parameterTypes = m1.getParameterTypes();
- getReturnType:获取返回类型的Class对象
Class<?> returnType = m1.getReturnType();
注意:
- 如果方法是非public的,调用方法操作需要先关闭访问检查
- 如果方法是static的,需要传入对象名的方法可以传入null
代码示例
public static void main(String[] args) throws Exception{
// 获取Dog类的Class对象
Class<?> dogClass = Class.forName("com.lsgstudy.reflection.Dog");
// 创建实例
Object o = dogClass.newInstance();
// public方法
// 获取方法对象
Method m1 = dogClass.getMethod("m1", int.class);
// 掉用方法
// 方法有返回值,返回值统一是Object
Object invoke = m1.invoke(o, 5);
System.out.println(invoke);
// private static方法
// 获取方法对象,private方法必须用getDeclaredMethod获取
Method m2 = dogClass.getDeclaredMethod("m2", String.class);
// 调用private方法需要关闭访问检查
m2.setAccessible(true);
// static 方法不需要这个类的对象
Object[] arr = (Object[])m2.invoke(null, "李华");
System.out.println(arr[1]);
}
构造器:Constructer
获取方法对象
- 获取public构造器
// getConstructor(参数类型Class对象列表)
Constructor<?> constructor = userClass.getConstructor(int.class);
- 获取所有构造器
// getDeclaredConstructor(参数类型Class对象列表)
Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(String.class, int.class);
常用方法
- newInstance:创建实例
// newInstance(方法参数(可变参数))
// 返回值统一是Object,可以向下转型
Object user2 = constructor.newInstance(15);
- getName:获取构造器名(全类名)
String name = constructor.getName();
- getModifiers:获取修饰符值
// 说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
// public(1) + static(8) = 9
int modifiers = constructor.getModifiers();
- getParameterTypes:获取参数数据类型的Class对象
Class<?>[] parameterTypes = constructor.getParameterTypes();
注意:非public的构造器直接创建实例会抛异常,需要关闭访问检查
代码示例
创建实例的三种方法
public static void main(String[] args) throws Exception{
// 获取User类的Class对象
Class<?> userClass = Class.forName("com.lsgstudy.reflection.User");
// 1. 用无参构造器创建实例
Object user1 = userClass.newInstance();
System.out.println(user1);
// 2. 用public有参构造器创建 实例
// 先获取构造器,再创建实例
Constructor<?> constructor = userClass.getConstructor(int.class);
Object user2 = constructor.newInstance(15);
System.out.println(user2);
// 3. 用非public有参构造器创建实例
// 获取构造器,因为是非public的,所有需要用getDeclaredConstructor
Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(String.class, int.class);
// 非public的构造器直接创建实例会抛异常,需要关闭访问检查
declaredConstructor.setAccessible(true);
Object user3 = declaredConstructor.newInstance("张三丰", 30);
System.out.println(user3);
}