• Java之反射机制


    • 概述

    • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
    • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。

    •  Class类

    Class类的理解

    • 在Object类中定义了以下的方法,此方法 将被所有子类继承: public final Class getClass() 该方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。 
    • 对象通过反射(可以理解为照镜子)后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含 了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
    • 一个加载的类在 JVM 中只会有一个Class实例
      1. int[] a = new int[10];
      2. int[] b = new int[100];
      3. Class c10 = a.getClass();
      4. Class c11 = b.getClass();
      5. // 只要数组的元素类型与维度一样,就是同一个Class
      6. System.out.println(c10 == c11);

    Class类常用的方法

    • static Class forName(String name) 返回指定类名 name 的 Class 对象
    • Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例
    • getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型 或void)名称
    • Class getSuperClass() 返回当前Class对象的父类的Class对象
    • Class [] getInterfaces() 获取当前Class对象的接口
    • ClassLoader getClassLoader() 返回该类的类加载器
    • Class getSuperclass() 返回表示此Class所表示的实体的超类的
    • Class Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
    • Field[] getDeclaredFields() 返回Field对象的一个数组
    • Method getMethod(String name,Class … paramTypes) 返回一个Method对象,此对象的形参类型为paramType 

     获取Class类的实例

    方式1:通过运行时类的对象,调用getClass()

    1. Person p = new Person();
    2. Classextends Person> calzz = p.getClass();
    3. System.out.println(calzz);

     方式2:调用Class的静态方法:forName(String classPath)

    1. Class clazz1 = Class.forName("day10.Person");
    2. System.out.println(clazz1);

     方式3:调用运行时类的属性:.class

    1. Class clazz2 = Person.class;
    2. System.out.println(clazz2);

    方式4:使用类的加载器:ClassLoader

    1. ClassLoader classLoader = ClassModelTest.class.getClassLoader();
    2. Class calzz3 = classLoader.loadClass("day10.Person");
    3. System.out.println(calzz3);

    理解:万事万物皆对象

    1. Class c1 = Object.class;//类
    2. Class c2 = Comparable.class;//接口
    3. Class c3 = String[].class;
    4. Class c4 = int[][].class;//数组
    5. Class c5 = ElementType.class;//枚举
    6. Class c6 = Override.class;//异常
    7. Class c7 = int.class;//基本数据类型
    8. Class c8 = void.class;//空参
    9. Class c9 = Class.class;
    •  类的加载

    类加载的过程

    当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过 如下三个步骤来对该类进行初始化。 加载

    • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。换句话说,Class的实例就对应着一个运行时类。
    • 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

    链接

    •  将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。

    初始化

    • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初化。 
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
    1. public class ClassLoadingTest {
    2. public static void main(String[] args) {
    3. System.out.println(A.m);
    4. }
    5. }
    6. class A {
    7. static {
    8. m = 1;
    9. }
    10. static int m = 2;
    11. }
    12. //第二步:链接结束后m=0
    13. //第三步:初始化后,m的值由()方法执行决定
    14. // 这个A的类构造器()方法由类变量的赋值和静态代码块中的语句按照顺序合并
    15. 产生,类似于
    16. // (){
    17. // m = 1;
    18. // m = 2;
    19. // }

     了解类的初始化

    会发生初始化的情况

    类的主动引用(一定会发生类的初始化)

    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类 

    不会发生类的初始化

    类的被动引用(不会发生类的初始化)

    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化;当通过子类引用父类的静态变量,不会导致子类初始化
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常 量池中了) 

    • ClassLoader(类的加载器)的理解

    类的加载器作用

    • 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为 方法区中类数据的访问入口。
    • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器 中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。 

    类加载器的分类

    • 引导类加载器(Bootstap ClassLoader):用C++编写的,是JVM自带的类 加载器,负责Java平台核心库,用来装载核心类 库。该加载器无法直接获取 
    • 扩展类加载器(Extension ClassLoader):负责jre/lib/ext目录下的jar包或 – D java.ext.dirs 指定目录下的jar包装入工作库
    • 系统类加载器(System ClassLoader):负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
    • 自定义类加载器

    类加载器常用方法

    getParent():获取父类的加载器

    1. public static void main(String[] args) throws ClassNotFoundException {
    2. //获取一个系统类加载器
    3. ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    4. System.out.println(classLoader);
    5. //获取系统类加载器的父类加载器,即扩展类加载器
    6. ClassLoader classLoader1 = classLoader.getParent();
    7. System.out.println(classLoader1);
    8. //获取扩展类加载器的父类加载器,即引导类加载器
    9. ClassLoader classLoader2 = classLoader1.getParent();
    10. System.out.println(classLoader2);//null
    11. //获取类由哪个类加载器进行加载
    12. ClassLoader classLoader4 = Class.forName("java.lang.Object").getClassLoader();
    13. System.out.println(classLoader4);
    14. }

    getResourceAsStream(): 获取类路 径下的指定文件的输入流

    应用场景:Properties:用来读取配置文件。

    1. @Test
    2. public void test2() throws IOException {
    3. Properties pros = new Properties();
    4. //读取配置文件的方式1
    5. //此时的文件默认在当前的module的src下下。
    6. ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    7. InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
    8. pros.load(is);
    9. //读取配置文件的方式2
    10. //此时的文件默认在当前的module下。
    11. // FileInputStream fis = new FileInputStream("jdbc.properties");
    12. // pros.load(fis);
    13. String user = pros.getProperty("user");
    14. String possWord = pros.getProperty("passWord");
    15. System.out.println("user=" + user + "\t" + "passWord=" + possWord);
    16. }
    • 创建运行时类的对象

    通过调用Class对象的newInstance()方法

    前提条件

    • 类必须有一个无参数的构造器。
    • 类的构造器的访问权限需要足够。(通常,设置为public)

    小知识点:在javabean中为什么要求提供一个public的空参构造器?

    •  便于通过反射,创建运行时类的对象
    • 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

    代码演示

    1. @Test
    2. public void test4() throws Exception {
    3. Class clazz = (Class) Class.forName("day10.Person");
    4. Person person = clazz.newInstance();
    5. System.out.println(person);//Person{name='null', age=0}
    6. }

    使用非空构造器创建对象

    1. @Test
    2. public void test5() throws Exception {
    3. Person p = new Person();
    4. Classextends Person> clazz = p.getClass();
    5. //获取指定的构造器,声明构造器的参数列表
    6. Constructorextends Person> constructor = clazz.getDeclaredConstructor(String.class);
    7. //保证次构造器可以访问(构造器的权限为public时,该步可省略)
    8. constructor.setAccessible(true);
    9. //使用获取的该构造器创造对象
    10. Person person = constructor.newInstance("嘻戏");
    11. System.out.println(person);
    12. }
    • 获取运行时类的完整结构

    运行时类的属性

    获取允许时类的属性及相关数据

    • getFields()获取当前运行时类及其父类中声明为public访问权限的属性
    • getDeclaredFields()获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
    • getModifiers()获取属性的权限修饰符
    • getType()获取属性的数据类型
    • getName()获取属性的名称
    1. @Test
    2. public void test6() throws ClassNotFoundException {
    3. ClassLoader classLoader = ClassModelTest.class.getClassLoader();
    4. Class clazz = classLoader.loadClass("day10.Person");
    5. Field[] fields = clazz.getDeclaredFields();
    6. for (int i = 0; i < fields.length; i++) {
    7. System.out.println(fields[i]);
    8. //获取权限修饰符
    9. int modifiers = fields[i].getModifiers();
    10. System.out.print("权限修饰符:" + Modifier.toString(modifiers) + "\t");
    11. //获取属性数据类型
    12. Class type = fields[i].getType();
    13. System.out.print("数据类型:" + type.getName() + "\t");
    14. //获取变量名称
    15. String fName = fields[i].getName();
    16. System.out.print("变量名" + fName);
    17. System.out.println();
    18. }
    19. }

    设置运行时类的属性

    1. @Test
    2. public void test6() throws Exception {
    3. //实例化运行实例对象
    4. Class clazz1 = Class.forName("day10.Person");
    5. //创建运行时类的对象
    6. Person person = (Person) clazz1.newInstance();
    7. //获取运行时类中指定变量名的属性
    8. Field name = clazz1.getDeclaredField("name");
    9. //保证当前属性是可访问的
    10. name.setAccessible(true);
    11. //设置属性的值
    12. name.set(person, "Jane");
    13. //获取属性的值
    14. System.out.println(name.get(person));
    15. }

    运行时类方法

    获取运行时类的方法及相关数据

    •  getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
    • getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
    • getAnnotations():获取方法声明的注解
    • getModifiers():获取方法权限修饰符
    • getReturnType():获取方法返回值类型
    • getName():获取方法名
    • getParameterTypes():获取方法形参列表
    • getExceptionTypes():获取方法抛出的异常
    1. @Test
    2. public void test7(){
    3. Person p = new Person();
    4. Classextends Person> clazz = p.getClass();
    5. Method[] methods = clazz.getDeclaredMethods();
    6. for (Method m : methods) {
    7. //获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
    8. System.out.println("运行时类方法:" + m + "\t");
    9. //获取方法声明的注解
    10. Annotation[] annotations = m.getAnnotations();
    11. for (Annotation a : annotations) {
    12. System.out.print("注解:" + a + "\t");
    13. }
    14. //获取方法权限修饰符
    15. System.out.print("方法权限修饰符:" + Modifier.toString(m.getModifiers()) + "\t");
    16. //获取方法返回值类型
    17. System.out.print("返回值类型:" + m.getReturnType().getName() + "\t");
    18. //获取方法名
    19. System.out.print("方法名:" + m.getName() + "\t");
    20. //获取方法形参列表
    21. Class[] parameterTypes = m.getParameterTypes();
    22. for (int i = 0; i < parameterTypes.length; i++) {
    23. if (i == parameterTypes.length -1) {
    24. System.out.print("args_" + i + "=" + parameterTypes[i].getName() + "\t");
    25. break;
    26. }
    27. if (i == 0)
    28. System.out.print("形参列表:" + "args_" + i + "=" + parameterTypes[i].getName() + ",");
    29. System.out.print("args_" + i + "=" + parameterTypes[i].getName() + ",");
    30. }
    31. //获取方法抛出的异常
    32. Class[] exceptionTypes = m.getExceptionTypes();
    33. if (exceptionTypes.length > 0) {
    34. System.out.print("异常:");
    35. for (int i = 0; i < exceptionTypes.length; i++) {
    36. if (i == exceptionTypes.length - 1) {
    37. System.out.print(exceptionTypes[i].getName());
    38. break;
    39. }
    40. System.out.print(exceptionTypes[i].getName() + ",");
    41. }
    42. }
    43. System.out.println();
    44. }
    45. }

     设置运行时类方法

     方法有返回值则返回对应的返回值,如果调用的运行时类中的方法没有返回值,则此invoke()返回null

    1. @Test
    2. public void test7() throws Exception {
    3. //实例化运行实例对象
    4. Class clazz1 = Person.class;
    5. //创建运行时类的对象
    6. Person person = clazz1.newInstance();
    7. //获取指定的某个方法
    8. Method show = clazz1.getDeclaredMethod("show", String.class, int.class);
    9. //保证当前方法是可访问的
    10. show.setAccessible(true);
    11. //设置方法的形参
    12. Object returnValue = show.invoke(person, "Jane", 23);
    13. System.out.println(returnValue);
    14. Method voidModel = clazz1.getDeclaredMethod("voidModel", Person.class);
    15. voidModel.setAccessible(true);
    16. }

    调用静态方法

    1. Method model = clazz1.getDeclaredMethod("model");
    2. model.setAccessible(true);
    3. // Object o = model.invoke(null);
    4. Object o = model.invoke(Person.class);//该处null/Person.class没有意义,因为invoke方法需要传入参数
    5. System.out.println(o);

    运行时类构造器

    获取运行时类的构造器及相关数据 

    • getConstructors():获取当前运行时类中声明为public的构造器
    • getDeclaredConstructors():获取当前运行时类中声明的所有的构造器 
    1. @Test
    2. public void test8() throws ClassNotFoundException {
    3. Class clazz = Class.forName("day10.Person");
    4. Constructor[] constructors = clazz.getDeclaredConstructors();
    5. for (Constructor c : constructors) {
    6. System.out.println(c);
    7. }
    8. }

    调用运行时类中的指定的构造器

    • getConstructor/getDeclaredConstructor(Class... parameterTypes) 通过指明构造器的参数列表获取指定的构造器
    1. @Test
    2. public void test9() throws Exception {
    3. Person p = new Person();
    4. Classextends Person> clazz = p.getClass();
    5. //获取指定的构造器
    6. Constructorextends Person> constructor = clazz.getDeclaredConstructor(String.class);
    7. //保证此构造器是可访问的
    8. constructor.setAccessible(true);
    9. //调用此构造器创建运行时类的对象
    10. Person person = constructor.newInstance("Jane");
    11. System.out.println(person);
    12. }

     运行时类父类

    • getSuperClass():获取运行时类的父类
    • getGenericSuperClass():获取运行时类的带泛型的父类
    1. @Test
    2. public void test10() throws Exception {
    3. ClassLoader classLoader = ClassModelTest.class.getClassLoader();
    4. Class clazz = classLoader.loadClass("day10.Person");
    5. //获取运行时类的父类
    6. Class superclass = clazz.getSuperclass();
    7. System.out.println(superclass);
    8. //获取运行时类的带泛型的父类
    9. Type genericSuperclass = clazz.getGenericSuperclass();
    10. System.out.println(genericSuperclass);
    11. }
    • getActualTypeArguments():获取运行时类的带泛型的父类的泛型
    1. @Test
    2. public void test11() throws ClassNotFoundException {
    3. Class clazz = Class.forName("day10.Person");
    4. Type genericSuperclass = clazz.getGenericSuperclass();
    5. ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
    6. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
    7. // System.out.println(actualTypeArguments[0].getTypeName());
    8. System.out.println(((Class)actualTypeArguments[0]).getName());
    9. }

     运行时类实现的接口

    • getInterfaces():获取运行时类实现的接口 
    • getSuperClass().getInterfaces():获取运行时类的父类实现的接口
    1. @Test
    2. public void test12(){
    3. Person p = new Person();
    4. Classextends Person> clazz = p.getClass();
    5. //获取运行时类实现的接口
    6. Class[] interfaces = clazz.getInterfaces();
    7. for (Class c: interfaces) {
    8. System.out.println(c);
    9. }
    10. System.out.println();
    11. //获取运行时类父类实现的接口
    12. Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
    13. for (Class c1 : interfaces1) {
    14. System.out.println(c1);
    15. }
    16. }

    获取运行时类所在的包

    • getPackage():获取运行时类所在的包
    1. @Test
    2. public void test13() throws Exception {
    3. ClassLoader classLoader = ClassModelTest.class.getClassLoader();
    4. Class clazz = classLoader.loadClass("day10.Person");
    5. Package aPackage = clazz.getPackage();
    6. System.out.println(aPackage);
    7. }

    获取运行时类声明的注解

    • getAnnotations():获取运行时类声明的注解
    1. @Test
    2. public void test14(){
    3. Class clazz = Person.class;
    4. Annotation[] annotations = clazz.getAnnotations();
    5. for (Annotation a : annotations) {
    6. System.out.println(a);
    7. }
    8. }

  • 相关阅读:
    Docker学习笔记
    openfeign服务调用实现
    SpringBoot配置连接两个或多个数据库
    PHP文件导入msql显示出错怎么回事
    pytorch 多卡分布式训练 调用all_gather_object 出现阻塞等待死锁的问题
    嵌入式开发:嵌入式基础——软件错误分类
    『C语言』深度走入取整 & 4种函数
    LabVIEW编程LabVIEW开发ITECH IT6000D系列电源例程与相关资料
    SpringCloud ——@RefreshScope
    5.Docker数据管理
  • 原文地址:https://blog.csdn.net/m0_58052874/article/details/126724747