万字长文详解Java反射技术 | JavaSE | Java进阶知识 | 源码

🙋大家好!我是毛毛张!
🌈个人首页: 神马都会亿点点的毛毛张

📌今天分享的是JavaSE中的进阶知识🛑:反射技术。内容有点长,非常全面,记得点赞👍、收藏✅加关注⭐之后再观看哦! ✈️

文章目录

  • 0.前言🍇
  • 1.反射的概述🍈
  • 2.Class类🍍
    • 2.1 概述🥭
    • 2.2 API简介🍎
  • 3.反射的使用🍏
    • 3.1 获取Class对象(三种方式)🍐
    • 3.2 获取成员变量以及获取值和修改值🍑
    • 3.3 获取构造方法并创建对象🍒
    • 3.4 获取成员方法🍓
    • 3.5 获取成员方法并运行🫐
  • 4 反射的作用🥝
    • 4.1 根据反射理解泛型擦除🍅
    • 4.2 修改字符串的内容🫒
    • 4.3 反射和配置文件结合动态获取(重点)🥥
    • 4.4 利用反射保存对象中的信息(重点)🥑
  • 5 反射机制执行的流程🍆
    • 5.1 案例解释🥔
    • 5.2 反射获取类实例forName底层🥕
    • 5.3 反射获取方法🌽
    • 5.4 调用 method.invoke() 方法🫑
    • 5.5 反射调用流程小结🥦
  • 参考文献🎃


0.前言🍇

  • 📍一个类有成员变量、构造方法、成员方法等组成,成员变量由还包括修饰符、成员变量名称、变量类型等组成,成员方法有修饰符、方法名称、方法形参和返回值等组成,这些组成部分我们在看类的源码的时候可以很清楚的就知道,但是给我们一个Java类,能不能让我们在不看源码的前提下就获取这个内容呢❓ 这就是我们今天要讲的反射技术❗

1.反射的概述🍈

  • 🍉专业解释(了解一下): Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制
  • 🍊通俗理解: 通过反射技术把Java中的一个类的各种组成部分进行解剖,把类中的各种组成部分一一映射成一个个Java对象,同时还可以无视修饰符调用类里面的内容
  • 🍋作用:
    • 通过反射可以对类的成员变量、成员方法和构造方法的信息进行编程访问
    • 可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中,读取到什么类,就创建什么类的对象;读取到什么方法,就调用什么方法;这样当需求变更的时候不需要修改代码,只要修改配置文件即可。
  • 🍍反射获取的内容:
类的成分解剖的内容
字段(成员变量)修饰符、名称、类型、赋值、获取值
构造方法修饰符、名称、形参、创建对象
成员方法修饰符、名字、形参、返回值、抛出的异常、注解、运行方法
  • 🍌Java是一种面向对象的编程语言。每个类都有类似的组成部分,为了便于操作和获取这些组成部分,Java提供了一个名为Class的类,也称为字节码文件对象,Class类使我们能够抽象和操作类的各种组成部分。
  • 🍿让我们首先来区别一下这两个概念再进入下面的学习:字节码文件和字节码文件对象
    • Java文件:这是我们编写的Java代码文件,后缀名为.java
    • 字节码文件:这是Java文件编译后的产物,后缀名为.class的文件,这些文件在硬盘上真实存在,可以直接查看。
    • 字节码文件对象:当.class文件被加载到内存中时,Java虚拟机自动创建的对象。这个对象包含了类的构造方法、成员变量和成员方法。我们通过反射机制获取的就是这个字节码文件对象,它在内存中是唯一的。
  • 下面就来介绍一下Class类!

2.Class类🍍

2.1 概述🥭

  • 我们知道一个Java对象在编译之后,就会得到一个以该类名为文件名,以class为后缀名的该类的字节码文件类名.class,Java通过读取该类的字节码文件,并为之创建一个字节码文件对象,也就是Class对象,通过调用Class类中的方法来获取上述内容
  • 让我们通过一个类的正常加载过程图解来理解反射:
    image-20240725204103374

2.2 API简介🍎

  • API文档中对Class类的简介如下:
    image-20240725213923724
  • 总结:
    • Class类的实例表示正在运行的Java应用程序中的类和接口,也就是说:JVM的每一个类都有一个该类的Class对象,包括基本数据类型、void
    • Class类没有公共构造构造方法,Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass()方法自动构造的,也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了

3.反射的使用🍏

  • 关于反射的应用,我们主要掌握以下四种应用:
    • 如何获取class字节码文件的对象
    • 利用反射如何获取构造方法(创建对象)
    • 利用反射如何获取成员变量(赋值,获取值)
    • 利用反射如何获取成员方法(运行)
  • 由于反射是一门解剖类的技术,于是首先需要创建一个类,毛毛张首先在这里创建一个Student类,下面围绕反射的使用都基于该类展开
    package com.reflect.csdn;public class Student {private String name;//姓名private int age;//年龄protected int pay;//薪资public String gender;//性别//无参构造方法public Student() {}//私有有参构造方法private Student(String name, int age){this.name = name;this.age = age;}//公共有参构造方法public Student(String name, int age, int pay, String gender) {this.name = name;this.age = age;this.pay = pay;this.gender = gender;}/*** 获取** @return name*/public String getName() {return name;}/*** 设置** @param name*/public void setName(String name) {this.name = name;}/*** 获取** @return age*/public int getAge() {return age;}/*** 设置** @param age*/public void setAge(int age) {this.age = age;}/*** 获取** @return pay*/public int getPay() {return pay;}/*** 设置** @param pay*/public void setPay(int pay) {this.pay = pay;}/*** 获取** @return gender*/public String getGender() {return gender;}/*** 设置** @param gender*/public void setGender(String gender) {this.gender = gender;}public String toString() {return "Student{name = " + name + ", age = " + age + ", pay = " + pay + ", gender = " + gender + "}";}//私有方法private void study() {System.out.println(this.name + "想偷偷卷,不想让别人知道他在学习,于是把该方法设置成私有方法");}//公共方法public void playGame() {System.out.println(this.name + "表现出对外玩游戏的方法,让别人都知道他只知道玩");}//私有方法 有返回值private String returnPay(){return this.name + "的薪资是:" + this.pay;}}
    

3.1 获取Class对象(三种方式)🍐

  • 获取Class对象有三种方式:
    • 方式1(最常用):通过Class类里面的静态方法forName(String className),传入的实参必须是全类名=包名+类名,例如:com.heima.javase.Student
    • 方式2:通过类的class属性来获取,例如:Student.class
    • 方式3:通过类实例化之后的对象的getClass()成员方法来获取,例如:stu.getClass()
      • 解释:getClass()是Object类中的方法,由于所有类都继承Object类,因此每个类都可以调用getClass()方法来获取Class对象
  • 上面三种方式对应着代码的不同运行阶段:
    image-20240725215924704
  • Class类获取名称常见方法:
方法名说明
forName()(1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
(2)为了产生Class引用,forName()立即就进行了初始化。
getName()取全限定的类名(包括包名),即类的完整名字。
getSimpleName()获取类名(不包括包名)
getCanonicalName()获取全限定的类名(包括包名)
isInterface()判断Class对象是否是表示一个接口
getInterfaces()返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss()返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
  • 代码演示1:
    package com.reflect.csdn;public class ReflectTest {public static void main(String[] args) throws ClassNotFoundException {/*获取class对象的三种方式:1.Class.forName("全类名");2.类名.class3.对象.getClass();*///方式1:Class这个类里面的静态方法forName//Class.forName("类的全类名"): 全类名 = 包名 + 类名Class class1 = Class.forName("com.reflect.csdn.Student");//源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象//clazz 就表示Student这个类的字节码文件对象。//就是当Student.class这个文件加载到内存之后,产生的字节码文件对象//方式2:通过class属性获取//类名.class//一般更多的是当作参数进行传递Class class2 = Student.class;//因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的System.out.println(class1 == class2);//true//方式3:通过Student对象获取字节码文件对象//当我们已经有了这个类的对象是,才可以使用Student s = new Student();Class class3 = s.getClass();System.out.println(class1 == class2);//trueSystem.out.println(class2 == class3);//true}
    }
    
  • 代码演示2: 如果存在继承关系、还要接口
    package com.reflect.csdn;
    import java.lang.reflect.Field;
    interface I1 {
    }
    interface I2 {
    }
    class Cell{public int mCellPublic;
    }
    class Animal extends  Cell{private int mAnimalPrivate;protected int mAnimalProtected;int mAnimalDefault;public int mAnimalPublic;private static int sAnimalPrivate;protected static int sAnimalProtected;static int sAnimalDefault;public static int sAnimalPublic;
    }
    class Dog extends Animal implements I1, I2 {private int mDogPrivate;public int mDogPublic;protected int mDogProtected;private int mDogDefault;private static int sDogPrivate;protected static int sDogProtected;static int sDogDefault;public static int sDogPublic;
    }
    public class Test {public static void main(String[] args) throws IllegalAccessException, InstantiationException {Class<Dog> dog = Dog.class;//类名打印System.out.println(dog.getName()); //com.reflect.csdn.DogSystem.out.println(dog.getSimpleName()); //DogSystem.out.println(dog.getCanonicalName());//com.reflect.csdn.Dog//接口System.out.println(dog.isInterface()); //falsefor (Class iI : dog.getInterfaces()) {System.out.println(iI);}/*interface com.reflect.csdn.I1interface com.reflect.csdn.I2*///父类System.out.println(dog.getSuperclass());//class com.reflect.csdn.Animal//创建对象Dog d = dog.newInstance();//字段for (Field f : dog.getFields()) {System.out.println(f.getName());}/*mDogPublicsDogPublicmAnimalPublicsAnimalPublicmCellPublic  //父类的父类的公共字段也打印出来了*/System.out.println("---------");for (Field f : dog.getDeclaredFields()) {System.out.println(f.getName());}/** 只有自己类声明的字段mDogPrivatemDogPublicmDogProtectedmDogDefaultsDogPrivatesDogProtectedsDogDefaultsDogPublic*/}
    }
  • getName、getCanonicalName与getSimpleName的区别:
    • getSimpleName:只获取类名
    • getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName
    • getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了
    • 代码演示:
      package com.reflect.csdn;
      public class Test {private  class inner{}public static void main(String[] args) throws ClassNotFoundException {//普通类System.out.println(Test.class.getSimpleName()); //TestSystem.out.println(Test.class.getName()); //com.reflect.csdn.TestSystem.out.println(Test.class.getCanonicalName()); //com.reflect.csdn.Test//内部类System.out.println(inner.class.getSimpleName()); //innerSystem.out.println(inner.class.getName()); //com.reflect.csdn.Test$innerSystem.out.println(inner.class.getCanonicalName()); //com.reflect.csdn.Test.inner//数组System.out.println(args.getClass().getSimpleName()); //String[]System.out.println(args.getClass().getName()); //[Ljava.lang.String;System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]//我们不能用getCanonicalName去加载类对象,必须用getName//Class.forName(inner.class.getCanonicalName()); 报错Class.forName(inner.class.getName());}
      }
      

3.2 获取成员变量以及获取值和修改值🍑

  • 获取成员变量使用到的是Class类中的下列方法:
方法名说明
public Field[] getFields()获取修饰符为public的字段,包含继承字段
public Field[] getDeclaredFields()获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
public Field getField(String name)获取指定name名称、具有public修饰的字段,包含继承字段
public Field getDeclaredField(String name)获取指定name名称的(包含private修饰的)字段,不包括继承的字段
  • 从上面方法中我们可以看出通过Class类中的方法获取的成员变量对象都是一个Filed对象
    • 如果我们想要获取成员变量或者修改成员变量的值,则需要使用Filed类中的方法
    • 如果成员变量的修饰符是公共的可以直接获取,如果不是公共的,必须要临时修改访问权限,否则无法使用
    • 如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可
    • 如果需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。
  • Field类常用的方法如下: 其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值;setAccessible(boolean flag)用于设置字段的临时访问权限,例如:如果是私有成员变量,通过该方法让其可以访问
方法方法说明
public void set(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
public Object get(Object obj)返回指定对象上此 Field 表示的字段的值
public void setAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
public String getName()返回此 Field 对象表示的字段的名称
public int getModifiers()返回此字段的权限修饰符,1代表public,2代表private,4代表protected
public Class<?> getType()返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
public boolean isEnumConstant()如果此字段表示枚举类型的元素则返回 true;否则返回 false
public String toGenericString()返回一个描述此 Field(包括其一般类型)的字符串
public Class<?> getDeclaringClass()返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
  • 代码演示1: 测试Class类和Field类中的方法
    package com.reflect.csdn;import java.lang.reflect.Field;public class ReflectTest2 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {/*利用反射获取成员变量Class类中用于获取成员变量的方法| Field[] getFields()                 | 返回所有成员变量对象的数组(只能拿public的) || Field[] getDeclaredFields()         | 返回所有成员变量对象的数组,存在就能拿到     || Field getField(String name)         | 返回单个成员变量对象(只能拿public的)       || Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到           |Field类中用于创建对象的方法void set(Object obj,Object value) 赋值Object get(Object obj)  获取值*//* 获取成员变量对象 *///1.获取Class对象Class stuClass = Class.forName("com.reflect.csdn.Student");//2.获取所有公共修饰符修饰的成员变量(public)System.out.println("========获取所有公共修饰符修饰的成员变量(public)=======");Field[] fields = stuClass.getFields();for (Field field : fields) {System.out.println("public修饰的成员属性:" + field);}//3.获取所修饰符修饰的成员变量(public + protected + 默认 + private)System.out.println("========获取所修饰符修饰的成员变量(public + protected + 默认 + private)=======");Field[] declaredFields = stuClass.getDeclaredFields();for (Field declaredField : declaredFields) {System.out.println("所有成员变量:" + declaredField);}//4.获取单个成员变量对象//如果获取的成员变量是不存在的,那么会报异常//Field aaa = stuClass.getField("aaa");//System.out.println(aaa);//NoSuchFieldExceptionSystem.out.println("=========获取单个成员变量对象============");Field gender = stuClass.getField("gender");System.out.println("单个成员变量:" + gender);//获取成员变量修饰符int modifiers = gender.getModifiers();System.out.println("该成员变量的修饰符编号:" + modifiers);//5.获取单个私有成员变量对象System.out.println("=========== 获取单个私有成员变量对象==============");Field name = stuClass.getDeclaredField("name");System.out.println("单个私有成员变量:" + name);//获取成员变量修饰符modifiers = gender.getModifiers();System.out.println("该成员变量的修饰符编号:" + modifiers);//初始化两个对象System.out.println("======= 初始化两个学生对象 ========");Student stu1 = new Student("张三", 23, 20000, "男");Student stu2 = new Student("李四", 24, 30000, "男");System.out.println("学生1的初始信息:" + stu1.toString());System.out.println("学生2的初始信息:" + stu2.toString());System.out.println("======= 获取和修改私有属性 修改学生1的年龄 ========");//获取age成员变量对象,并修改学生1的年龄为:25Field ageField = stuClass.getDeclaredField("age");//由于age属于private成员变量属性,不能直接访问,需要临时修改访问权限ageField.setAccessible(true);int age = (int) ageField.get(stu1);System.out.println("学生1的原始年龄:" + age);//执行修改ageField.set(stu1, 25);System.out.println("学生1的修改后的年龄:" + ageField.get(stu1));System.out.println("======= 获取和修改公共属性 修改学生2的性别 ========");//获取genger成员变量对象,并修改学生2的性别为:女Field genderField = stuClass.getDeclaredField("gender");String g = (String) genderField.get(stu2);System.out.println("学生2的原始性别:" + g);//由于gender属于public成员变量属性,可以直接访问//执行修改genderField.set(stu2, "女");System.out.println("学生2的修改后的性别:" + genderField.get(stu2));}
    }
  • 代码输出:
    ========获取所有公共修饰符修饰的成员变量(public)=======
    public修饰的成员属性:public java.lang.String com.reflect.csdn.Student.gender
    ========获取所修饰符修饰的成员变量(public + protected + 默认 + private)=======
    所有成员变量:private java.lang.String com.reflect.csdn.Student.name
    所有成员变量:private int com.reflect.csdn.Student.age
    所有成员变量:protected int com.reflect.csdn.Student.pay
    所有成员变量:public java.lang.String com.reflect.csdn.Student.gender
    =========获取单个成员变量对象============
    单个成员变量:public java.lang.String com.reflect.csdn.Student.gender
    该成员变量的修饰符编号:1
    =========== 获取单个私有成员变量对象==============
    单个私有成员变量:private java.lang.String com.reflect.csdn.Student.name
    该成员变量的修饰符编号:1
    ======= 初始化两个学生对象 ========
    学生1的初始信息:Student{name = 张三, age = 23, pay = 20000, gender =}
    学生2的初始信息:Student{name = 李四, age = 24, pay = 30000, gender =}
    ======= 获取和修改私有属性 修改学生1的年龄 ========
    学生1的原始年龄:23
    学生1的修改后的年龄:25
    ======= 获取和修改公共属性 修改学生2的性别 ========
    学生2的原始性别:男
    学生2的修改后的性别:女
    
  • 代码演示2: 如果有继承关系
    public class ReflectField {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {Class<?> clazz = Class.forName("reflect.Student");//获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,// 否则抛NoSuchFieldExceptionField field = clazz.getField("age");System.out.println("field:"+field);//获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取Field fields[] = clazz.getFields();for (Field f:fields) {System.out.println("f:"+f.getDeclaringClass());}System.out.println("================getDeclaredFields====================");//获取当前类所字段(包含private字段),注意不包含父类的字段Field fields2[] = clazz.getDeclaredFields();for (Field f:fields2) {System.out.println("f2:"+f.getDeclaringClass());}//获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段Field field2 = clazz.getDeclaredField("desc");System.out.println("field2:"+field2);}/**输出结果: field:public int reflect.Person.agef:public java.lang.String reflect.Student.descf:public int reflect.Person.agef:public java.lang.String reflect.Person.name================getDeclaredFields====================f2:public java.lang.String reflect.Student.descf2:private int reflect.Student.scorefield2:public java.lang.String reflect.Student.desc*/
    }class Person{public int age;public String name;//省略set和get方法
    }class Student extends Person{public String desc;private int score;//省略set和get方法
    }
    

3.3 获取构造方法并创建对象🍒

  • 获取构造方法使用到的是Class类中的下列方法:
方法名说明
Constructor<?>[] getConstructors()返回所有公共构造方法对象的数组(只能public修饰)
Constructor<?>[] getDeclaredConstructors()获得所有构造方法对象的数组(包含private修饰)
Constructor<T> getConstructor(Class<?>... parameterTypes)返回当个公共构造方法对象(只能public修饰)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)返回当个构造方法对象(包含private修饰)
  • 从上面方法中我们可以看出通过Class类中的方法获取的构造方法对象都是一个Constructor对象
    • 如果我们想要操作获取的构造方法,则需要使用Constructor类中的方法
    • 如果构造方法的修饰符是公共的可以直接获取,如果不是公共的,必须要临时修改访问权限,否则无法使用
  • Constructor类常用的方法如下:
方法方法说明
public T newInstance()调用无参构造器创建此 Class 对象所表示的类的一个新实例
public void setAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
public Class getDeclaringClass()返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
public Type[] getGenericParameterTypes()按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型
public String getName()以字符串形式返回此构造方法的名称
public Class<?>[] getParameterTypes()按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型
public String toGenericString()返回描述此 Constructor 的字符串,其中包括类型参数
  • Constructor类方法说明:
    • 其中T newInstance()是根据获取的构造方法对象来实例化一个对象
    • 同样,如果该构造方法是私有构造方法需要通过setAccessible(boolean flag)来临时修改访问权限
  • 代码演示:
package com.reflect.csdn;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class ReflectTest3 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {/*Class类中用于获取构造方法的方法| Constructor<?>[] getConstructors()                           | 获得所有的构造(只能public修饰)  || Constructor<?>[] getDeclaredConstructors()                   | 获得所有的构造(包含private修饰) || Constructor<T> getConstructor(Class<?>... parameterTypes)    | 获取指定构造(只能public修饰)    || Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | 获取指定构造(包含private修饰)   |Constructor类中用于创建对象的方法T newInstance(Object... initargs)setAccessible(boolean flag)*///1.获取字节码文件对象Class stuClass = Class.forName("com.reflect.csdn.Student");//2.获取公共构造方法System.out.println("=========== 获取所有 public 构造方法============");Constructor[] constructors = stuClass.getConstructors();for (Constructor constructor : constructors) {System.out.println("public修饰的构造方法:" + constructor);}//3.获取所有构造方法System.out.println("=========== 获取所有构造方法 ===========");Constructor[] declaredConstructors = stuClass.getDeclaredConstructors();for (Constructor declaredConstructor : declaredConstructors) {System.out.println("所有构造方法:" + declaredConstructor);}//4.获取公共无参构造方法并实例化对象System.out.println("=========== 获取公共无参构造方法并实例化对象 ===========");Constructor noArgsConstructor = stuClass.getConstructor();System.out.println("public 无参构造" + noArgsConstructor);//实例化对象Student stu1 = (Student) noArgsConstructor.newInstance();System.out.println("空参构造实例化对象输出:" + stu1);//5.获取公共全参构造方法并实例化对象System.out.println("=========== 获取公共全参构造方法并实例化对象 ===========");Constructor fullArgsConstructor = stuClass.getConstructor(String.class,int.class, int.class, String.class);System.out.println("public 全参构造" + fullArgsConstructor);//实例化对象Student stu2 = (Student) fullArgsConstructor.newInstance("张三",23,20000,"男");System.out.println("全参构造实例化对象输出:" + stu2);//6.获取私有有参构造方法并实例化对象System.out.println("=========== 获取私有有参构造方法并实例化对象 ===========");Constructor constructor = stuClass.getDeclaredConstructor(String.class, int.class);System.out.println("private 私有构造" + constructor);//由于使用私有化构造方法来实例化对象,需要打开临时访问权限constructor.setAccessible(true);//实例化对象Student stu3 = (Student) constructor.newInstance("李四",24);System.out.println("空参构造实例化对象输出:" + stu3);}
}
  • 代码输出:
    =========== 获取所有 public 构造方法============
    public修饰的构造方法:public com.reflect.csdn.Student(java.lang.String,int,int,java.lang.String)
    public修饰的构造方法:public com.reflect.csdn.Student()
    =========== 获取所有构造方法 ===========
    所有构造方法:public com.reflect.csdn.Student(java.lang.String,int,int,java.lang.String)
    所有构造方法:private com.reflect.csdn.Student(java.lang.String,int)
    所有构造方法:public com.reflect.csdn.Student()
    =========== 获取公共无参构造方法并实例化对象 ===========
    public 无参构造public com.reflect.csdn.Student()
    空参构造实例化对象输出:Student{name = null, age = 0, pay = 0, gender = null}
    =========== 获取公共全参构造方法并实例化对象 ===========
    public 全参构造public com.reflect.csdn.Student(java.lang.String,int,int,java.lang.String)
    全参构造实例化对象输出:Student{name = 张三, age = 23, pay = 20000, gender =}
    =========== 获取私有有参构造方法并实例化对象 ===========
    private 私有构造private com.reflect.csdn.Student(java.lang.String,int)
    空参构造实例化对象输出:Student{name = 李四, age = 24, pay = 0, gender = null}
    

3.4 获取成员方法🍓

  • 获取构造方法使用到的是Class类中的下列方法:
方法名称方法说明
Method getDeclaredMethod(String name, Class<?>... parameterTypes)返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method[] getDeclaredMethods()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
Method getMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[] getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
  • 从上面方法中我们可以看出通过Class类中的方法获取的成员方法对象都是一个Method对象
    • 如果我们想要操作获取的成员方法,则需要使用Method类中的方法
    • 如果成员方法的修饰符是公共的可以直接获取,如果不是公共的,必须要临时修改访问权限,否则无法使用
    • 在通过getMethods()方法获取Method对象时,会把父类的方法也获取到
    • getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法
  • 代码演示1:
    import java.lang.reflect.Method;public class ReflectMethod  {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {Class clazz = Class.forName("reflect.Circle");//根据参数获取public的Method,包含继承自父类的方法Method method = clazz.getMethod("draw",int.class,String.class);System.out.println("method:"+method);//获取所有public的方法:Method[] methods =clazz.getMethods();for (Method m:methods){System.out.println("m::"+m);}System.out.println("=========================================");//获取当前类的方法包含private,该方法无法获取继承自父类的methodMethod method1 = clazz.getDeclaredMethod("drawCircle");System.out.println("method1::"+method1);//获取当前类的所有方法包含private,该方法无法获取继承自父类的methodMethod[] methods1=clazz.getDeclaredMethods();for (Method m:methods1){System.out.println("m1::"+m);}}
    }class Shape {public void draw(){System.out.println("draw");}public void draw(int count , String name){System.out.println("draw "+ name +",count="+count);}}
    class Circle extends Shape{private void drawCircle(){System.out.println("drawCircle");}public int getAllCount(){return 100;}
    }================ 代码输出:================
    method:public void reflect.Shape.draw(int,java.lang.String)m::public int reflect.Circle.getAllCount()
    m::public void reflect.Shape.draw()
    m::public void reflect.Shape.draw(int,java.lang.String)
    m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
    m::public boolean java.lang.Object.equals(java.lang.Object)
    m::public java.lang.String java.lang.Object.toString()
    m::public native int java.lang.Object.hashCode()
    m::public final native java.lang.Class java.lang.Object.getClass()
    m::public final native void java.lang.Object.notify()
    m::public final native void java.lang.Object.notifyAll()=========================================
    method1::private void reflect.Circle.drawCircle()m1::public int reflect.Circle.getAllCount()
    m1::private void reflect.Circle.drawCircle()

3.5 获取成员方法并运行🫐

  • Method类常用的方法如下:
方法方法说明
public Object invoke(Object obj, Object... args)对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
public void setAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
public Class<?> getReturnType()返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型
gpublic Type getGenericReturnType()返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。
public Class<?>[] getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组
public Type[] getGenericParameterTypes()按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型
public String getName()以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
public boolean isVarArgs()判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
public String toGenericString()返回描述此 Method 的字符串,包括类型参数。
  • Method类方法说明:
    • 如果该构造方法是私有构造方法需要通过setAccessible(boolean flag)来临时修改访问权限
    • 我们可以通过Method类的invoke(Object obj,Object... args)方法来动态调用类的方法并运行,其中,第一个参数代表调用的对象,第二个参数传递的调用方法的参数(如果没有可以不写),返回值如果没有可以不写
    • getReturnType()/getGenericReturnType()都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息
    • getParameterTypes()/getGenericParameterTypes()也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的
  • 代码示例1: 通过Method对象调用指定类的方法
    package com.reflect;public class ReflectTest{public static void main(String[] args){Class clazz = Class.forName("reflect.Circle");//创建对象Circle circle = (Circle) clazz.newInstance();//获取指定参数的方法对象MethodMethod method = clazz.getMethod("draw",int.class,String.class);//通过Method对象的invoke(Object obj,Object... args)方法调用method.invoke(circle,15,"圈圈");//对私有无参方法的操作Method method1 = clazz.getDeclaredMethod("drawCircle");//修改私有方法的访问标识method1.setAccessible(true);method1.invoke(circle);//对有返回值得方法操作Method method2 =clazz.getDeclaredMethod("getAllCount");Integer count = (Integer) method2.invoke(circle);System.out.println("count:"+count);/*输出结果:draw 圈圈,count=15drawCirclecount:100*/}
    }class Shape {public void draw(){System.out.println("draw");}public void draw(int count , String name){System.out.println("draw "+ name +",count="+count);}}
    class Circle extends Shape{private void drawCircle(){System.out.println("drawCircle");}public int getAllCount(){return 100;}
    }
    
  • 代码示例2:
package com.reflect;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class ReflectDemo6 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {//1.获取字节码文件对象Class clazz = Class.forName("com.reflect.Student");//2.获取一个对象//需要用这个对象去调用方法Student s = new Student();//3.获取一个指定的方法//参数一:方法名//参数二:参数列表,如果没有可以不写Method eatMethod = clazz.getMethod("eat",String.class);//运行//参数一:表示方法的调用对象//参数二:方法在运行时需要的实际参数//注意点:如果方法有返回值,那么需要接收invoke的结果//如果方法没有返回值,则不需要接收String result = (String) eatMethod.invoke(s, "重庆小面");System.out.println(result);}
}============================================================package com.reflect;public class Student {private String name;private int age;public String gender;public String address;public Student() {}public Student(String name) {this.name = name;}private Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}private void study(){System.out.println("学生在学习");}private void sleep(){System.out.println("学生在睡觉");}public String eat(String something){System.out.println("学生在吃" + something);return "学生已经吃完了,非常happy";}
}

4 反射的作用🥝

  • 反射的作用:
    • 获取一个类里面的所有的信息,获取到了之后,再执行其他的业务逻辑。但是这种操作在开发中一般不用,都是框架底层来用的。
    • 反射可以跟配置文件结合起来使用,动态的创建对象并调用方法

4.1 根据反射理解泛型擦除🍅

  • 集合中的泛型只在java文件中存在,当编译成class文件之后,就没有泛型了,所以是可以通过反射越过泛型检查的
  • 代码解释:
    package com.itheima.reflectdemo;import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;public class ReflectDemo8 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {//1.创建集合对象ArrayList<Integer> list = new ArrayList<>();list.add(123);//list.add("aaa");//2.利用反射运行add方法去添加字符串//因为反射使用的是class字节码文件//获取class对象Class clazz = list.getClass();//获取add方法对象Method method = clazz.getMethod("add", Object.class);//运行方法method.invoke(list,"aaa");//打印集合System.out.println(list);}
    }

4.2 修改字符串的内容🫒

  • 在前面学习字符串的时候,字符串定义之后就不能修改,这是因为字符串,在底层是一个byte类型的字节数组,名字叫做value,并且被finalprivate修饰。
    private final byte[] value;
    

image-20240726155303916

  • 字符串不能修改的真正原因:
    • final修饰value表示value记录的地址值不能修改
    • private修饰value而且没有对外提供getvalue和setvalue的方法
    • 所以,在外界不能获取或修改value记录的地址值
  • 如果要强行修改可以用反射:
    String s = "abc";
    String ss = "abc";
    // private final byte[] value= {97,98,99};
    // 没有对外提供getvalue和setvalue的方法,不能修改value记录的地址值
    // 如果我们利用反射获取了value的地址值。
    // 也是可以修改的,final修饰的value
    // 真正不可变的value数组的地址值,里面的内容利用反射还是可以修改的,比较危险//1.获取class对象
    Class clazz = s.getClass();//2.获取value成员变量(private)
    Field field = clazz.getDeclaredField("value");
    //但是这种操作非常危险
    //JDK高版本已经屏蔽了这种操作,低版本还是可以的
    //临时修改权限
    field.setAccessible(true);//3.获取value记录的地址值
    byte[] bytes = (byte[]) field.get(s);
    bytes[0] = 100;System.out.println(s);//dbc
    System.out.println(ss);//dbc
    

4.3 反射和配置文件结合动态获取(重点)🥥

需求: 利用反射根据文件中的不同类名和方法名,创建不同的对象并调用方法。
分析:
①通过Properties加载配置文件
②得到类名和方法名
③通过类名反射得到Class对象
④通过Class对象创建一个对象
⑤通过Class对象得到方法
⑥调用方法
代码实现:
配置文件中的信息:文件名prop.properties

classname=com.reflect.Student
methodname=study
public class ReflectDemo9 {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {/*反射可以跟配置文件结合的方式,动态的创建对象,并调用方法*///1.读取配置文件中的信息Properties prop = new Properties();FileInputStream fis = new FileInputStream("prop.properties");prop.load(fis);fis.close();System.out.println(prop);//2.获取全类名和方法名String classname = (String) prop.get("classname");String method = (String) prop.get("method");System.out.println(classname);System.out.println(method);//3.利用反射创建对象并运行方法Class clazz = Class.forName(classname);//获取构造方法Constructor con = clazz.getDeclaredConstructor();Object o = con.newInstance();System.out.println(o);//运行方法Method m = clazz.getDeclaredMethod(method);//不知道修饰符,设置一下可调用权限m.setAccessible(true);//开始运行m.invoke(o);}
}/* 学生类 */
package com.reflect;public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public void study() {System.out.println("学生正在学习");}/*** 获取** @return name*/public String getName() {return name;}/*** 设置** @param name*/public void setName(String name) {this.name = name;}/*** 获取** @return age*/public int getAge() {return age;}/*** 设置** @param age*/public void setAge(int age) {this.age = age;}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}
}

4.4 利用反射保存对象中的信息(重点)🥑

public class MyReflectDemo {public static void main(String[] args) throws IllegalAccessException, IOException {/*对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去*/Student s = new Student("小A",23,'女',167.5,"睡觉");Teacher t = new Teacher("播妞",10000);saveObject(s);}//把对象里面所有的成员变量名和值保存到本地文件中public static void saveObject(Object obj) throws IllegalAccessException, IOException {//1.获取字节码文件的对象Class clazz = obj.getClass();//2. 创建IO流BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\\a.txt"));//3. 获取所有的成员变量Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {field.setAccessible(true);//获取成员变量的名字String name = field.getName();//获取成员变量的值Object value = field.get(obj);//写出数据bw.write(name + "=" + value);bw.newLine();}bw.close();}
}
public class Student {private String name;private int age;private char gender;private double height;private String hobby;public Student() {}public Student(String name, int age, char gender, double height, String hobby) {this.name = name;this.age = age;this.gender = gender;this.height = height;this.hobby = hobby;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}/*** 获取* @return gender*/public char getGender() {return gender;}/*** 设置* @param gender*/public void setGender(char gender) {this.gender = gender;}/*** 获取* @return height*/public double getHeight() {return height;}/*** 设置* @param height*/public void setHeight(double height) {this.height = height;}/*** 获取* @return hobby*/public String getHobby() {return hobby;}/*** 设置* @param hobby*/public void setHobby(String hobby) {this.hobby = hobby;}public String toString() {return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}";}
}
public class Teacher {private String name;private double salary;public Teacher() {}public Teacher(String name, double salary) {this.name = name;this.salary = salary;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return salary*/public double getSalary() {return salary;}/*** 设置* @param salary*/public void setSalary(double salary) {this.salary = salary;}public String toString() {return "Teacher{name = " + name + ", salary = " + salary + "}";}
}

5 反射机制执行的流程🍆

  • 这部分内容是结合源码来理解理解反射的底层执行机制,有点难,对于刚学的小伙伴可以选择跳过

5.1 案例解释🥔

代码:

public class HelloReflect {public static void main(String[] args) {try {// 1. 使用外部配置的实现,进行动态加载类TempFunctionTest test = (TempFunctionTest)Class.forName("com.tester.HelloReflect").newInstance();test.sayHello("call directly");// 2. 根据配置的函数名,进行方法调用(不需要通用的接口抽象)Object t2 = new TempFunctionTest();Method method = t2.getClass().getDeclaredMethod("sayHello", String.class);method.invoke(test, "method invoke");} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchMethodException e ) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}public void sayHello(String word) {System.out.println("hello," + word);}}

执行流程图解:
img

5.2 反射获取类实例forName底层🥕

首先调用了 java.lang.Class 的静态方法,获取类信息

    @CallerSensitivepublic static Class<?> forName(String className)throws ClassNotFoundException {// 先通过反射,获取调用进来的类信息,从而获取当前的 classLoaderClass<?> caller = Reflection.getCallerClass();// 调用native方法进行获取class信息return forName0(className, true, ClassLoader.getClassLoader(caller), caller);}

forName()反射获取类信息,并没有将实现留给了java,而是交给了jvm去加载

主要是先获取 ClassLoader, 然后调用 native 方法,获取信息,加载类则是回调 java.lang.ClassLoader

最后,jvm又会回调 ClassLoader 进类加载

    // public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}// sun.misc.Launcherpublic Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {int var3 = var1.lastIndexOf(46);if(var3 != -1) {SecurityManager var4 = System.getSecurityManager();if(var4 != null) {var4.checkPackageAccess(var1.substring(0, var3));}}if(this.ucp.knownToNotExist(var1)) {Class var5 = this.findLoadedClass(var1);if(var5 != null) {if(var2) {this.resolveClass(var5);}return var5;} else {throw new ClassNotFoundException(var1);}} else {return super.loadClass(var1, var2);}}// java.lang.ClassLoaderprotected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{// 先获取锁synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded// 如果已经加载了的话,就不用再加载了Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {// 双亲委托加载if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}// 父类没有加载到时,再自己加载if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}protected Object getClassLoadingLock(String className) {Object lock = this;if (parallelLockMap != null) {// 使用 ConcurrentHashMap来保存锁Object newLock = new Object();lock = parallelLockMap.putIfAbsent(className, newLock);if (lock == null) {lock = newLock;}}return lock;}protected final Class<?> findLoadedClass(String name) {if (!checkName(name))return null;return findLoadedClass0(name);}

下面来看一下 newInstance() 的实现方式,newInstance() 主要做了三件事:

  1. 权限检测,如果不通过直接抛出异常;
  2. 查找无参构造器,并将其缓存起来;
  3. 调用具体方法的无参构造方法,生成实例并返回;

newInstance()源码:

    // 首先肯定是 Class.newInstance@CallerSensitivepublic T newInstance()throws InstantiationException, IllegalAccessException{if (System.getSecurityManager() != null) {checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);}// NOTE: the following code may not be strictly correct under// the current Java memory model.// Constructor lookup// newInstance() 其实相当于调用类的无参构造函数,所以,首先要找到其无参构造器if (cachedConstructor == null) {if (this == Class.class) {// 不允许调用 Class 的 newInstance() 方法throw new IllegalAccessException("Can not call newInstance() on the Class for java.lang.Class");}try {// 获取无参构造器Class<?>[] empty = {};final Constructor<T> c = getConstructor0(empty, Member.DECLARED);// Disable accessibility checks on the constructor// since we have to do the security check here anyway// (the stack depth is wrong for the Constructor's// security check to work)java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {public Void run() {c.setAccessible(true);return null;}});cachedConstructor = c;} catch (NoSuchMethodException e) {throw (InstantiationException)new InstantiationException(getName()).initCause(e);}}Constructor<T> tmpConstructor = cachedConstructor;// Security check (same as in java.lang.reflect.Constructor)int modifiers = tmpConstructor.getModifiers();if (!Reflection.quickCheckMemberAccess(this, modifiers)) {Class<?> caller = Reflection.getCallerClass();if (newInstanceCallerCache != caller) {Reflection.ensureMemberAccess(caller, this, null, modifiers);newInstanceCallerCache = caller;}}// Run constructortry {// 调用无参构造器return tmpConstructor.newInstance((Object[])null);} catch (InvocationTargetException e) {Unsafe.getUnsafe().throwException(e.getTargetException());// Not reachedreturn null;}}

下面是获取构造器的过程: getConstructor0() 为获取匹配的构造方器,分三步:

  1. 先获取所有的constructors, 然后通过进行参数类型比较;
  2. 找到匹配后,通过 ReflectionFactory copy一份constructor返回;
  3. 否则抛出 NoSuchMethodException;
    private Constructor<T> getConstructor0(Class<?>[] parameterTypes,int which) throws NoSuchMethodException{// 获取所有构造器Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));for (Constructor<T> constructor : constructors) {if (arrayContentsEq(parameterTypes,constructor.getParameterTypes())) {return getReflectionFactory().copyConstructor(constructor);}}throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));}

privateGetDeclaredConstructors()获取所有的构造器主要步骤:

  1. 先尝试从缓存中获取;
  2. 如果缓存没有,则从jvm中重新获取,并存入缓存,缓存使用软引用进行保存,保证内存可用;
    // 获取当前类所有的构造方法,通过jvm或者缓存// Returns an array of "root" constructors. These Constructor// objects must NOT be propagated to the outside world, but must// instead be copied via ReflectionFactory.copyConstructor.private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {checkInitted();Constructor<T>[] res;// 调用 reflectionData(), 获取保存的信息,使用软引用保存,从而使内存不够可以回收ReflectionData<T> rd = reflectionData();if (rd != null) {res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;// 存在缓存,则直接返回if (res != null) return res;}// No cached value available; request value from VMif (isInterface()) {@SuppressWarnings("unchecked")Constructor<T>[] temporaryRes = (Constructor<T>[]) new Constructor<?>[0];res = temporaryRes;} else {// 使用native方法从jvm获取构造器res = getDeclaredConstructors0(publicOnly);}if (rd != null) {// 最后,将从jvm中读取的内容,存入缓存if (publicOnly) {rd.publicConstructors = res;} else {rd.declaredConstructors = res;}}return res;}// Lazily create and cache ReflectionDataprivate ReflectionData<T> reflectionData() {SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;int classRedefinedCount = this.classRedefinedCount;ReflectionData<T> rd;if (useCaches &&reflectionData != null &&(rd = reflectionData.get()) != null &&rd.redefinedCount == classRedefinedCount) {return rd;}// else no SoftReference or cleared SoftReference or stale ReflectionData// -> create and replace new instancereturn newReflectionData(reflectionData, classRedefinedCount);}// 新创建缓存,保存反射信息private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,int classRedefinedCount) {if (!useCaches) return null;// 使用cas保证更新的线程安全性,所以反射是保证线程安全的while (true) {ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);// try to CAS it...if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {return rd;}// 先使用CAS更新,如果更新成功,则立即返回,否则测查当前已被其他线程更新的情况,如果和自己想要更新的状态一致,则也算是成功了oldReflectionData = this.reflectionData;classRedefinedCount = this.classRedefinedCount;if (oldReflectionData != null &&(rd = oldReflectionData.get()) != null &&rd.redefinedCount == classRedefinedCount) {return rd;}}}

另外,使用 relactionData() 进行缓存保存;ReflectionData 的数据结构如下:

    // reflection data that might get invalidated when JVM TI RedefineClasses() is calledprivate static class ReflectionData<T> {volatile Field[] declaredFields;volatile Field[] publicFields;volatile Method[] declaredMethods;volatile Method[] publicMethods;volatile Constructor<T>[] declaredConstructors;volatile Constructor<T>[] publicConstructors;// Intermediate results for getFields and getMethodsvolatile Field[] declaredPublicFields;volatile Method[] declaredPublicMethods;volatile Class<?>[] interfaces;// Value of classRedefinedCount when we created this ReflectionData instancefinal int redefinedCount;ReflectionData(int redefinedCount) {this.redefinedCount = redefinedCount;}}

其中,还有一个点,就是如何比较构造是否是要查找构造器,其实就是比较类型完成相等就完了,有一个不相等则返回false。

    private static boolean arrayContentsEq(Object[] a1, Object[] a2) {if (a1 == null) {return a2 == null || a2.length == 0;}if (a2 == null) {return a1.length == 0;}if (a1.length != a2.length) {return false;}for (int i = 0; i < a1.length; i++) {if (a1[i] != a2[i]) {return false;}}return true;}// sun.reflect.ReflectionFactory/** Makes a copy of the passed constructor. The returnedconstructor is a "child" of the passed one; see the commentsin Constructor.java for details. */public <T> Constructor<T> copyConstructor(Constructor<T> arg) {return langReflectAccess().copyConstructor(arg);}// java.lang.reflect.Constructor, copy 其实就是新new一个 Constructor 出来Constructor<T> copy() {// This routine enables sharing of ConstructorAccessor objects// among Constructor objects which refer to the same underlying// method in the VM. (All of this contortion is only necessary// because of the "accessibility" bit in AccessibleObject,// which implicitly requires that new java.lang.reflect// objects be fabricated for each reflective call on Class// objects.)if (this.root != null)throw new IllegalArgumentException("Can not copy a non-root Constructor");Constructor<T> res = new Constructor<>(clazz,parameterTypes,exceptionTypes, modifiers, slot,signature,annotations,parameterAnnotations);// root 指向当前 constructorres.root = this;// Might as well eagerly propagate this if already presentres.constructorAccessor = constructorAccessor;return res;}

通过上面,获取到 Constructor 了。

接下来就只需调用其相应构造器的 newInstance(),即返回实例了。

    // return tmpConstructor.newInstance((Object[])null); // java.lang.reflect.Constructor@CallerSensitivepublic T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor;   // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}// sun.reflect.DelegatingConstructorAccessorImplpublic Object newInstance(Object[] args)throws InstantiationException,IllegalArgumentException,InvocationTargetException{return delegate.newInstance(args);}// sun.reflect.NativeConstructorAccessorImplpublic Object newInstance(Object[] args)throws InstantiationException,IllegalArgumentException,InvocationTargetException{// We can't inflate a constructor belonging to a vm-anonymous class// because that kind of class can't be referred to by name, hence can't// be found from the generated bytecode.if (++numInvocations > ReflectionFactory.inflationThreshold()&& !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) {ConstructorAccessorImpl acc = (ConstructorAccessorImpl)new MethodAccessorGenerator().generateConstructor(c.getDeclaringClass(),c.getParameterTypes(),c.getExceptionTypes(),c.getModifiers());parent.setDelegate(acc);}// 调用native方法,进行调用 constructorreturn newInstance0(c, args);}

返回构造器的实例后,可以根据外部进行进行类型转换,从而使用接口或方法进行调用实例功能了。

5.3 反射获取方法🌽

  • 第一步,先获取 Method;
    // java.lang.Class@CallerSensitivepublic Method getDeclaredMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);if (method == null) {throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));}return method;}

忽略第一个检查权限,剩下就只有两个动作了。

  1. 获取所有方法列表;
  2. 根据方法名称和方法列表,选出符合要求的方法;
  3. 如果没有找到相应方法,抛出异常,否则返回对应方法;

所以,先看一下怎样获取类声明的所有方法?

    // Returns an array of "root" methods. These Method objects must NOT// be propagated to the outside world, but must instead be copied// via ReflectionFactory.copyMethod.private Method[] privateGetDeclaredMethods(boolean publicOnly) {checkInitted();Method[] res;ReflectionData<T> rd = reflectionData();if (rd != null) {res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;if (res != null) return res;}// No cached value available; request value from VMres = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));if (rd != null) {if (publicOnly) {rd.declaredPublicMethods = res;} else {rd.declaredMethods = res;}}return res;}

很相似,和获取所有构造器的方法很相似,都是先从缓存中获取方法,如果没有,则从jvm中获取。

不同的是,方法列表需要进行过滤 Reflection.filterMethods;当然后面看来,这个方法我们一般不会派上用场。

    // sun.misc.Reflectionpublic static Method[] filterMethods(Class<?> containingClass, Method[] methods) {if (methodFilterMap == null) {// Bootstrappingreturn methods;}return (Method[])filter(methods, methodFilterMap.get(containingClass));}// 可以过滤指定的方法,一般为空,如果要指定过滤,可以调用 registerMethodsToFilter(), 或者...private static Member[] filter(Member[] members, String[] filteredNames) {if ((filteredNames == null) || (members.length == 0)) {return members;}int numNewMembers = 0;for (Member member : members) {boolean shouldSkip = false;for (String filteredName : filteredNames) {if (member.getName() == filteredName) {shouldSkip = true;break;}}if (!shouldSkip) {++numNewMembers;}}Member[] newMembers =(Member[])Array.newInstance(members[0].getClass(), numNewMembers);int destIdx = 0;for (Member member : members) {boolean shouldSkip = false;for (String filteredName : filteredNames) {if (member.getName() == filteredName) {shouldSkip = true;break;}}if (!shouldSkip) {newMembers[destIdx++] = member;}}return newMembers;}
  • 第二步,根据方法名和参数类型过滤指定方法返回
    private static Method searchMethods(Method[] methods,String name,Class<?>[] parameterTypes){Method res = null;// 使用常量池,避免重复创建StringString internedName = name.intern();for (int i = 0; i < methods.length; i++) {Method m = methods[i];if (m.getName() == internedName&& arrayContentsEq(parameterTypes, m.getParameterTypes())&& (res == null|| res.getReturnType().isAssignableFrom(m.getReturnType())))res = m;}return (res == null ? res : getReflectionFactory().copyMethod(res));}

大概意思看得明白,就是匹配到方法名,然后参数类型匹配,才可以。

  • 但是可以看到,匹配到一个方法,并没有退出for循环,而是继续进行匹配。
  • 这里是匹配最精确的子类进行返回(最优匹配)
  • 最后,还是通过 ReflectionFactory, copy 方法后返回。

5.4 调用 method.invoke() 方法🫑

    @CallerSensitivepublic Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, obj, modifiers);}}MethodAccessor ma = methodAccessor;             // read volatileif (ma == null) {ma = acquireMethodAccessor();}return ma.invoke(obj, args);}

invoke时,是通过 MethodAccessor 进行调用的,而 MethodAccessor 是个接口,在第一次时调用 acquireMethodAccessor() 进行新创建。

    // probably make the implementation more scalable.private MethodAccessor acquireMethodAccessor() {// First check to see if one has been created yet, and take it// if soMethodAccessor tmp = null;if (root != null) tmp = root.getMethodAccessor();if (tmp != null) {// 存在缓存时,存入 methodAccessor,否则调用 ReflectionFactory 创建新的 MethodAccessormethodAccessor = tmp;} else {// Otherwise fabricate one and propagate it up to the roottmp = reflectionFactory.newMethodAccessor(this);setMethodAccessor(tmp);}return tmp;}// sun.reflect.ReflectionFactorypublic MethodAccessor newMethodAccessor(Method method) {checkInitted();if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {return new MethodAccessorGenerator().generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers());} else {NativeMethodAccessorImpl acc =new NativeMethodAccessorImpl(method);DelegatingMethodAccessorImpl res =new DelegatingMethodAccessorImpl(acc);acc.setParent(res);return res;}}

两个Accessor详情:

//     NativeMethodAccessorImpl / DelegatingMethodAccessorImpl
class NativeMethodAccessorImpl extends MethodAccessorImpl {private final Method method;private DelegatingMethodAccessorImpl parent;private int numInvocations;NativeMethodAccessorImpl(Method method) {this.method = method;}public Object invoke(Object obj, Object[] args)throws IllegalArgumentException, InvocationTargetException{// We can't inflate methods belonging to vm-anonymous classes because// that kind of class can't be referred to by name, hence can't be// found from the generated bytecode.if (++numInvocations > ReflectionFactory.inflationThreshold()&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {MethodAccessorImpl acc = (MethodAccessorImpl)new MethodAccessorGenerator().generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers());parent.setDelegate(acc);}return invoke0(method, obj, args);}void setParent(DelegatingMethodAccessorImpl parent) {this.parent = parent;}private static native Object invoke0(Method m, Object obj, Object[] args);
}
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {private MethodAccessorImpl delegate;DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {setDelegate(delegate);}public Object invoke(Object obj, Object[] args)throws IllegalArgumentException, InvocationTargetException{return delegate.invoke(obj, args);}void setDelegate(MethodAccessorImpl delegate) {this.delegate = delegate;}
}

进行 ma.invoke(obj, args); 调用时,调用 DelegatingMethodAccessorImpl.invoke();

最后被委托到 NativeMethodAccessorImpl.invoke(), 即:

    public Object invoke(Object obj, Object[] args)throws IllegalArgumentException, InvocationTargetException{// We can't inflate methods belonging to vm-anonymous classes because// that kind of class can't be referred to by name, hence can't be// found from the generated bytecode.if (++numInvocations > ReflectionFactory.inflationThreshold()&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {MethodAccessorImpl acc = (MethodAccessorImpl)new MethodAccessorGenerator().generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers());parent.setDelegate(acc);}// invoke0 是个 native 方法,由jvm进行调用业务方法。从而完成反射调用功能。return invoke0(method, obj, args);}

其中, generateMethod() 是生成具体类的方法:

    /** This routine is not thread-safe */public MethodAccessor generateMethod(Class<?> declaringClass,String   name,Class<?>[] parameterTypes,Class<?>   returnType,Class<?>[] checkedExceptions,int modifiers){return (MethodAccessor) generate(declaringClass,name,parameterTypes,returnType,checkedExceptions,modifiers,false,false,null);}

generate() 戳详情。

    /** This routine is not thread-safe */private MagicAccessorImpl generate(final Class<?> declaringClass,String name,Class<?>[] parameterTypes,Class<?>   returnType,Class<?>[] checkedExceptions,int modifiers,boolean isConstructor,boolean forSerialization,Class<?> serializationTargetClass){ByteVector vec = ByteVectorFactory.create();asm = new ClassFileAssembler(vec);this.declaringClass = declaringClass;this.parameterTypes = parameterTypes;this.returnType = returnType;this.modifiers = modifiers;this.isConstructor = isConstructor;this.forSerialization = forSerialization;asm.emitMagicAndVersion();// Constant pool entries:// ( * = Boxing information: optional)// (+  = Shared entries provided by AccessorGenerator)// (^  = Only present if generating SerializationConstructorAccessor)//     [UTF-8] [This class's name]//     [CONSTANT_Class_info] for above//     [UTF-8] "sun/reflect/{MethodAccessorImpl,ConstructorAccessorImpl,SerializationConstructorAccessorImpl}"//     [CONSTANT_Class_info] for above//     [UTF-8] [Target class's name]//     [CONSTANT_Class_info] for above// ^   [UTF-8] [Serialization: Class's name in which to invoke constructor]// ^   [CONSTANT_Class_info] for above//     [UTF-8] target method or constructor name//     [UTF-8] target method or constructor signature//     [CONSTANT_NameAndType_info] for above//     [CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info] for target method//     [UTF-8] "invoke" or "newInstance"//     [UTF-8] invoke or newInstance descriptor//     [UTF-8] descriptor for type of non-primitive parameter 1//     [CONSTANT_Class_info] for type of non-primitive parameter 1//     ...//     [UTF-8] descriptor for type of non-primitive parameter n//     [CONSTANT_Class_info] for type of non-primitive parameter n// +   [UTF-8] "java/lang/Exception"// +   [CONSTANT_Class_info] for above// +   [UTF-8] "java/lang/ClassCastException"// +   [CONSTANT_Class_info] for above// +   [UTF-8] "java/lang/NullPointerException"// +   [CONSTANT_Class_info] for above// +   [UTF-8] "java/lang/IllegalArgumentException"// +   [CONSTANT_Class_info] for above// +   [UTF-8] "java/lang/InvocationTargetException"// +   [CONSTANT_Class_info] for above// +   [UTF-8] "<init>"// +   [UTF-8] "()V"// +   [CONSTANT_NameAndType_info] for above// +   [CONSTANT_Methodref_info] for NullPointerException's constructor// +   [CONSTANT_Methodref_info] for IllegalArgumentException's constructor// +   [UTF-8] "(Ljava/lang/String;)V"// +   [CONSTANT_NameAndType_info] for "<init>(Ljava/lang/String;)V"// +   [CONSTANT_Methodref_info] for IllegalArgumentException's constructor taking a String// +   [UTF-8] "(Ljava/lang/Throwable;)V"// +   [CONSTANT_NameAndType_info] for "<init>(Ljava/lang/Throwable;)V"// +   [CONSTANT_Methodref_info] for InvocationTargetException's constructor// +   [CONSTANT_Methodref_info] for "super()"// +   [UTF-8] "java/lang/Object"// +   [CONSTANT_Class_info] for above// +   [UTF-8] "toString"// +   [UTF-8] "()Ljava/lang/String;"// +   [CONSTANT_NameAndType_info] for "toString()Ljava/lang/String;"// +   [CONSTANT_Methodref_info] for Object's toString method// +   [UTF-8] "Code"// +   [UTF-8] "Exceptions"//  *  [UTF-8] "java/lang/Boolean"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(Z)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "booleanValue"//  *  [UTF-8] "()Z"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "java/lang/Byte"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(B)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "byteValue"//  *  [UTF-8] "()B"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "java/lang/Character"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(C)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "charValue"//  *  [UTF-8] "()C"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "java/lang/Double"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(D)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "doubleValue"//  *  [UTF-8] "()D"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "java/lang/Float"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(F)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "floatValue"//  *  [UTF-8] "()F"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "java/lang/Integer"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(I)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "intValue"//  *  [UTF-8] "()I"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "java/lang/Long"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(J)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "longValue"//  *  [UTF-8] "()J"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "java/lang/Short"//  *  [CONSTANT_Class_info] for above//  *  [UTF-8] "(S)V"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for above//  *  [UTF-8] "shortValue"//  *  [UTF-8] "()S"//  *  [CONSTANT_NameAndType_info] for above//  *  [CONSTANT_Methodref_info] for aboveshort numCPEntries = NUM_BASE_CPOOL_ENTRIES + NUM_COMMON_CPOOL_ENTRIES;boolean usesPrimitives = usesPrimitiveTypes();if (usesPrimitives) {numCPEntries += NUM_BOXING_CPOOL_ENTRIES;}if (forSerialization) {numCPEntries += NUM_SERIALIZATION_CPOOL_ENTRIES;}// Add in variable-length number of entries to be able to describe// non-primitive parameter types and checked exceptions.numCPEntries += (short) (2 * numNonPrimitiveParameterTypes());asm.emitShort(add(numCPEntries, S1));final String generatedName = generateName(isConstructor, forSerialization);asm.emitConstantPoolUTF8(generatedName);asm.emitConstantPoolClass(asm.cpi());thisClass = asm.cpi();if (isConstructor) {if (forSerialization) {asm.emitConstantPoolUTF8("sun/reflect/SerializationConstructorAccessorImpl");} else {asm.emitConstantPoolUTF8("sun/reflect/ConstructorAccessorImpl");}} else {asm.emitConstantPoolUTF8("sun/reflect/MethodAccessorImpl");}asm.emitConstantPoolClass(asm.cpi());superClass = asm.cpi();asm.emitConstantPoolUTF8(getClassName(declaringClass, false));asm.emitConstantPoolClass(asm.cpi());targetClass = asm.cpi();short serializationTargetClassIdx = (short) 0;if (forSerialization) {asm.emitConstantPoolUTF8(getClassName(serializationTargetClass, false));asm.emitConstantPoolClass(asm.cpi());serializationTargetClassIdx = asm.cpi();}asm.emitConstantPoolUTF8(name);asm.emitConstantPoolUTF8(buildInternalSignature());asm.emitConstantPoolNameAndType(sub(asm.cpi(), S1), asm.cpi());if (isInterface()) {asm.emitConstantPoolInterfaceMethodref(targetClass, asm.cpi());} else {if (forSerialization) {asm.emitConstantPoolMethodref(serializationTargetClassIdx, asm.cpi());} else {asm.emitConstantPoolMethodref(targetClass, asm.cpi());}}targetMethodRef = asm.cpi();if (isConstructor) {asm.emitConstantPoolUTF8("newInstance");} else {asm.emitConstantPoolUTF8("invoke");}invokeIdx = asm.cpi();if (isConstructor) {asm.emitConstantPoolUTF8("([Ljava/lang/Object;)Ljava/lang/Object;");} else {asm.emitConstantPoolUTF8("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");}invokeDescriptorIdx = asm.cpi();// Output class information for non-primitive parameter typesnonPrimitiveParametersBaseIdx = add(asm.cpi(), S2);for (int i = 0; i < parameterTypes.length; i++) {Class<?> c = parameterTypes[i];if (!isPrimitive(c)) {asm.emitConstantPoolUTF8(getClassName(c, false));asm.emitConstantPoolClass(asm.cpi());}}// Entries common to FieldAccessor, MethodAccessor and ConstructorAccessoremitCommonConstantPoolEntries();// Boxing entriesif (usesPrimitives) {emitBoxingContantPoolEntries();}if (asm.cpi() != numCPEntries) {throw new InternalError("Adjust this code (cpi = " + asm.cpi() +", numCPEntries = " + numCPEntries + ")");}// Access flagsasm.emitShort(ACC_PUBLIC);// This classasm.emitShort(thisClass);// Superclassasm.emitShort(superClass);// Interfaces count and interfacesasm.emitShort(S0);// Fields count and fieldsasm.emitShort(S0);// Methods count and methodsasm.emitShort(NUM_METHODS);emitConstructor();emitInvoke();// Additional attributes (none)asm.emitShort(S0);// Load classvec.trim();final byte[] bytes = vec.getData();// Note: the class loader is the only thing that really matters// here -- it's important to get the generated code into the// same namespace as the target class. Since the generated code// is privileged anyway, the protection domain probably doesn't// matter.return AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {public MagicAccessorImpl run() {try {return (MagicAccessorImpl)ClassDefiner.defineClass(generatedName,bytes,0,bytes.length,declaringClass.getClassLoader()).newInstance();} catch (InstantiationException | IllegalAccessException e) {throw new InternalError(e);}}});}

咱们主要看这一句:ClassDefiner.defineClass(xx, declaringClass.getClassLoader()).newInstance();

ClassDefiner.defineClass方法实现中,每被调用一次都会生成一个DelegatingClassLoader类加载器对象 ,这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载。

而反射生成的类,有时候可能用了就可以卸载了,所以使用其独立的类加载器,从而使得更容易控制反射类的生命周期。

5.5 反射调用流程小结🥦

📌最后,用几句话总结反射的实现原理:

  1. 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;
  2. 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;
  3. 反射也是考虑了线程安全的,放心使用;
  4. 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;
  5. 反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;
  6. 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
  7. 调度反射方法,最终是由jvm执行invoke0()执行;

参考文献🎃

  • https://blog.csdn.net/kye055947/article/details/88377209?ops_request_misc=&request_id=&biz_id=102&utm_term=java反射&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-2-88377209.142^v100^pc_search_result_base6&spm=1018.2226.3001.4187
  • https://blog.csdn.net/weixin_45395059/article/details/126765905?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172190159816800172536758%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=172190159816800172536758&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-2-126765905-null-null.142^v100^pc_search_result_base6&utm_term=java反射&spm=1018.2226.3001.4187
  • https://pdai.tech/md/java/basic/java-basic-x-reflection.html#method类及其用法
  • 黑马程序员java

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/49762.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络世界】HTTP协议

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 概念 &#x1f4c1; URL &#x1f4c2; urlencode 和 urldecode &#x1f4c1; 协议格式 &#x1f4c1; 方法 &#x1f4c2; GET/get &#x1f4c2; POST/post &#x1f4c1; 常见的报头 &#x1f4c1; 状态码 &…

模型大小的指标和模型量化的指标和手段

一、模型大小的指标 1.计算量 计算次数&#xff0c;反映了模型对硬件计算单元的需求。计算量的单位是 OPs(Operations) 。最常用的数据格式 为 float32&#xff0c;因此float32类型下的计算量单位被写作 FLOPs (Floating Point Operations)&#xff0c;即浮点计算次数。模型的…

3D Web轻量化引擎HOOPS Communicator针对复杂大模型Web端可视化的解决方案

随着工程设计、制造和建筑领域中三维模型的日益复杂化&#xff0c;如何在Web端高效处理和展示这些大规模数据成为一大挑战。HOOPS Communicator作为一款强大的3D可视化工具&#xff0c;提供了一套针对复杂大模型的轻量化解决方案&#xff0c;涵盖了模型轻量化及格式转换、超大模…

PostgreSQL成为最受欢迎的数据库; TiDB马拉松大赛开启, Serverless和Vector为比赛焦点

重要更新 1. TiDB Hackathon大赛报名开启&#xff0c;总奖金达21万&#xff0c;主题是基于 TiDB Serverless 内置的向量搜索功能&#xff08;Vector Search&#xff09;构建 AI 创新应用&#xff0c;感兴趣的可以报名参加。( [1] ) 2. Stack Overflow 2024 开发者调研结果公布…

自学JavaScript(放假在家自学第一天)

目录 JavaScript介绍分为以下几点 1.1 JavaScript 是什么 1.2JavaScript书写位置 1.3 Javascript注释 1.4 Javascript结束符 1.5 Javascript输入输出语法 JavaScript(是什么?) 是一种运行在客户端(浏览器)的编程语言&#xff0c;实现人机交互效果。 2.作用(做什么?)网…

从头开始微调Llama 3.1模型

在今天的科技专栏中&#xff0c;我们将深入探讨如何微调Llama 3.1模型&#xff0c;以使其更好地适应您的特定领域数据。微调大型语言模型&#xff08;如Llama&#xff09;的主要目的是为了在特定领域的数据上表现更好&#xff0c;从而生成更符合您需求的输出。以下是我们将要介…

SpringBoot知识笔记

一、基本概念 1.1 特性 起步依赖 自动配置 其它特性:内嵌的Tomcat、Jetty(无需部署WAR文件),外部配置,不需要XML配置(properties/yml)。 1.2 配置文件 SpringBoot提供了多种属性配置方式 //application.properties server.port=9090 server.servlet.context-path…

Python爬虫知识体系-----Urllib库的使用

数据科学、数据分析、人工智能必备知识汇总-----Python爬虫-----持续更新&#xff1a;https://blog.csdn.net/grd_java/article/details/140574349 文章目录 1. 基本使用2. 请求对象的定制3. 编解码1. get请求方式&#xff1a;urllib.parse.quote&#xff08;&#xff09;2. ur…

邦布带你从零开始实现图书管理系统(java版)

今天我们来从零开始实现图书管理系统。 图书管理系统 来看我们的具体的实现&#xff0c;上述视频。 我们首先来实现框架&#xff0c;我们要实现图书管理系统&#xff0c;首先要搭框架。 我们首先定义一个书包&#xff0c;在书包中定义一个书类和一个书架类&#xff0c;再定义…

用Java手写jvm之实现查找class

写在前面 完成类加载器加载class的三阶段&#xff0c;加载&#xff0c;解析&#xff0c;初始化中的加载&#x1f600;&#x1f600;&#x1f600; 源码 。 jvm想要运行class&#xff0c;是根据类全限定名称来从特定的位置基于类加载器来查找的&#xff0c;分别如下&#xff1a;…

【SQL 新手教程 2/20】关系模型 -- 主键

&#x1f497; 关系数据库建立在关系模型上⭐ 关系模型本质上就是若干个存储数据的二维表 记录 (Record)&#xff1a; 表的每一行称为记录&#xff08;Record&#xff09;&#xff0c;记录是一个逻辑意义上的数据 字段 (Column)&#xff1a;表的每一列称为字段&#xff08;Colu…

吴恩达的TranslationAgent学习

TranslationAgent构成 整个[TranslationAgent (github.com)]在流程上分为短文本的一次性翻译和长文本的分chunk翻译&#xff08;按照Token进行划分&#xff09;。 但是不论长文本翻译还是短文本翻译&#xff0c;总体流程遵循执行、纠正再执行的逻辑循环实现。 这种按照自省思路…

【数字IC/FPGA】使用Verdi对比两个波形

步骤一&#xff1a; 使用verdi打开第一个波形 bsub verdi -ssf 1.fsdb添加需要观察的信号&#xff0c;如下图所示&#xff1a; 步骤二&#xff1a; 新建容器&#xff0c;依次点击Window --> Dock to --> New Container Window。 然后输入容器的名字&#xff0c;如下图所…

SQL数据库:通过在视频监控平台服务器上直接使用SQL存储过程,在海量记录中查询特定时间段内-某个摄像头的所有视频片段

目录 一、背景 1、存储过程 2、视频监控系统 二、需求和数据表 1、具体要求 2、数据表 3、部分数据 三、实现 1、目标 2、创建存储过程 &#xff08;1&#xff09;存储过程代码 &#xff08;2&#xff09;创建成功 3、存储过程的解释 4、SQL命令调用方式 5、调用…

Java----队列(Queue)

目录 1.队列&#xff08;Queue&#xff09; 1.1概念 1.2队列的使用 1.3队列的模拟实现 1.4循环队列 1.4.1循环队列下标偏移 1.4.2如何区分队列是空还是满 1.5双端队列 (Deque) 1.队列&#xff08;Queue&#xff09; 1.1概念 队列&#xff1a;只允许在一端进行插入数据…

Linux Redhat ens33不显示IP问题

优质博文&#xff1a;IT-BLOG-CN 【第一步】&#xff1a;查看系统网卡设备 : ip addr show 【第二步】&#xff1a;修改网卡配置参数 cd /etc/sysconfig/network-scripts/ vi ifcfg-ens33 修改ONBOOT参数为yes 【第三步】&#xff1a;重启网卡&#xff0c;然后ping检测…

奇怪的Excel单元格字体颜色格式

使用VBA代码修改单元格全部字符字体颜色是个很简单的任务&#xff0c;例如设置A1单元格字体颜色为红色。 Range("A1").Font.Color RGB(255, 0, 0)有时需要修改部分字符的颜色&#xff0c;如下图所示&#xff0c;将红色字符字体颜色修改为蓝色。代码将会稍许复杂&am…

Linux:Linux进程控制

目录 1. 进程概念 1.1 并行和并发 2. 进程创建 2.1 fork()函数初识 2.2 写时拷贝 2.3 fork常规用法 2.4 fork调用失败的原因 3. 进程终止 3.1 进程场景 3.2 进程常见退出方法 4. 进程等待 4.1 进程等待必要性 4.2 进程等待的方法 4.2.1 wait方法&#xff1a; 4.…

2024年起重信号司索工(建筑特殊工种)证模拟考试题库及起重信号司索工(建筑特殊工种)理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年起重信号司索工(建筑特殊工种)证模拟考试题库及起重信号司索工(建筑特殊工种)理论考试试题是由安全生产模拟考试一点通提供&#xff0c;起重信号司索工(建筑特殊工种)证模拟考试题库是根据起重信号司索工(建筑特…

2.9.GoogLeNet

GoogLeNet ​ 主要解决了什么样大小的卷积核是最合适的&#xff1a;有时使用不同大小的卷积核组合是有利的 1.Inception块 ​ Inception块由四条并行路径组成。 前三条路径使用窗口大小为11、33和55的卷积层&#xff0c;从不同空间大小中提取信息。 ​ 中间的两条路径在输入…