安全矩阵

 找回密码
 立即注册
搜索
查看: 874|回复: 0

java反射

[复制链接]

417

主题

417

帖子

2391

积分

金牌会员

Rank: 6Rank: 6

积分
2391
发表于 2023-9-11 17:28:20 | 显示全部楼层 |阅读模式
衡阳信安 2023-09-11 00:00 发表于湖南
上次的java反射没怎么懂,再来学习一下。
概念
Java反射是指在程序运行期间对于类的属性、方法、构造方法等进行动态访问和操作的机制。通过Java反射API,程序能够在运行期获取类的信息,操作类的属性、方法、构造方法,创建类的对象等。
不适用new创建对象而访问类中方法的过程即可成为反射
java反射的相关类位于java.lang.reflect.*;包
为什么要用反射
  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
  • 获取任意对象的属性,并且能改变对象的属性
  • 调用任意对象的方法
  • 判断任意一个对象所属的类
  • 实例化任意一个类的对象
  • 通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。

Class类相关类
java.lang.Class 代表整个字节码。代表一个类型,代表整个类。
java.lang.reflect.Method 代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor 代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field 代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)
注:必须先获取Class才能获取Method、Constructor、Field
那么如何获取Class实例呢?
为什么要获取Class实例
  • 实例化对象:通过 Class 对象可以创建一个类的实例,从而调用该类的属性和方法。
  • 获取类的信息:通过 Class 对象可以获取类的修饰符、字段、方法、构造器、注解等信息,从而可以动态地获取类的信息并进行操作。
  • 执行方法:通过 Class 对象可以获取类中的方法并调用,从而动态地执行方法。
  • 进行类型检查:通过 Class 对象可以进行类型检查,从而保证程序的类型安全。

获取Class类实例的三种方法
Class.forName(“完整类名带包名”)

  1. Class<?> cls = Class.forName("java.util.ArrayList");
复制代码
对象.getClass()
  1. Object obj = new String("Hello World");
  2. Class<? extends Object> objClass = obj.getClass();
复制代码
类名.class
  1. Class<String> strClass = String.class;
复制代码
Class类常用方法
getFields()—— 获得类的public类型的属性。
getDeclaredFields()—— 获得类的所有属性
getField(String name)—— 获得类的指定属性
getMethods()—— 获得类的public类型的方法
getMethod (String name,Class [] args)—— 获得类的指定方法
getConstrutors()—— 获得类的public类型的构造方法
getConstrutor(Class[] args)—— 获得类的特定构造方法
newInstance()—— 通过类的无参构造方法创建对象
getName()—— 获得类的完整名字
getPackage()—— 获取此类所属的包
getSuperclass()—— 获得此类的父类对应的Class对象
通过反射实例化对象
  1. 对象.newInstance()
复制代码
没有公共的无参构造函数,newInstance()方法就会抛出InstantiationException异常
其他方法
使用getDeclaredConstructor()方法获取特定的构造函数,并使用newInstance(Object... args)方法传递参数来创建对象实例
  1. Class<MyClass> cls = MyClass.class;
  2. Constructor<MyClass> constructor = cls.getDeclaredConstructor(int.class, String.class);
  3. MyClass myObj = constructor.newInstance(42, "Hello");
复制代码
Class.forName导致类加载
如果你只是希望一个类的静态代码块执行,其它代码一律不执行,可以使用:
  1. Class.forName("完整类名");
复制代码
这个方法的执行会导致类加载,类加载时,静态代码块执行。
反射Filed【反射/反编译一个类的属性】Class类方法
public T newInstance() 创建对象
public String getName() 返回完整类名带包名
public String getSimpleName() 返回类名
public Field[] getFields() 返回类中public修饰的属性
public Field[] getDeclaredFields() 返回类中所有的属性
public Field getDeclaredField(String name) 根据属性名name获取指定的属性
public native int getModifiers() 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Method[] getDeclaredMethods() 返回类中所有的实例方法
public Method getDeclaredMethod(String name, Class<?>… parameterTypes) 根据方法名name和方法形参获取指定方法
public Constructor<?>[] getDeclaredConstructors() 返回类中所有的构造方法
public Constructor getDeclaredConstructor(Class<?>… parameterTypes) 根据方法形参获取指定的构造方法
public native Class<? super T> getSuperclass() 返回调用类的父类
public Class<?>[] getInterfaces() 返回调用类实现的接口集合
Field类方法
public String getName() 返回属性名
public int getModifiers() 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?> getType() 以Class类型,返回属性类型【一般配合Class类的getSimpleName()方法使用】
public void set(Object obj, Object value) 设置属性值
public Object get(Object obj) 读取属性值
给属性赋值和读
直接赋值
  1. 对象.属性=值

  2. 对象.属性
复制代码
通过反射赋值
  1. 属性.set(对象, 值);

  2. 属性.get(对象);
复制代码
  1. /*
  2. 必须掌握:
  3.     怎么通过反射机制访问一个java对象的属性?
  4.         给属性赋值set
  5.         获取属性的值get
  6. */
  7. class ReflectTest07{
  8.     public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
  9.         //不使用反射机制给属性赋值
  10.         Student student = new Student();
  11.         /**给属性赋值三要素:给s对象的no属性赋值1111
  12.          * 要素1:对象s
  13.          * 要素2:no属性
  14.          * 要素3:1111
  15.          */
  16.         student.no = 1111;
  17.         /**读属性值两个要素:获取s对象的no属性的值。
  18.          * 要素1:对象s
  19.          * 要素2:no属性
  20.          */
  21.         System.out.println(student.no);

  22.         //使用反射机制给属性赋值
  23.         Class studentClass = Class.forName("javase.reflectBean.Student");//首先获取Class类
  24.         Object obj = studentClass.newInstance();//实例化为对象obj,obj就是Student对象。(底层调用无参数构造方法)

  25.         // 获取no属性(根据属性的名称来获取Field)
  26.         Field noField = studentClass.getDeclaredField("no");
  27.         // 给obj对象(Student对象)的no属性赋值
  28.         /*
  29.             虽然使用了反射机制,但是三要素还是缺一不可:
  30.                 要素1:obj对象
  31.                 要素2:no属性
  32.                 要素3:22222值
  33.             注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
  34.          */
  35.         noField.set(obj, 22222);

  36.         // 读取属性的值
  37.         // 两个要素:获取obj对象的no属性的值。
  38.         System.out.println(noField.get(obj));
  39.     }
复制代码

反射Method【反射/反编译一个类的方法】Method类方法
方法名 备注
public String getName() 返回方法名
public int getModifiers() 获取方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?> getReturnType() 以Class类型,返回方法类型【一般配合Class类的getSimpleName()方法使用】
public Class<?>[] getParameterTypes() 返回方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public Object invoke(Object obj, Object… args) 调用方法
  1. 方法.invoke(对象, 实参);
复制代码
  1. /*
  2. 重点:必须掌握,通过反射机制怎么调用一个对象的方法?
  3.     五颗星*****

  4.     反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,
  5.     将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,
  6.     但是java代码不需要做任何改动。这就是反射机制的魅力。
  7. */
  8. class ReflectTest10{
  9.     public static void main(String[] args) throws Exception {
  10.         // 不使用反射机制,怎么调用方法
  11.         // 创建对象
  12.         UserService userService = new UserService();
  13.         // 调用方法
  14.         /*
  15.             要素分析:
  16.                 要素1:对象userService
  17.                 要素2:login方法名
  18.                 要素3:实参列表
  19.                 要素4:返回值
  20.          */
  21.         System.out.println(userService.login("admin", "123") ? "登入成功!" : "登入失败!");

  22.         //使用反射机制调用方法
  23.         Class userServiceClass = Class.forName("javase.reflectBean.UserService");
  24.         // 创建对象
  25.         Object obj = userServiceClass.newInstance();
  26.         // 获取Method
  27.         Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
  28. //        Method loginMethod = userServiceClass.getDeclaredMethod("login");//注:没有形参就不传
  29.         // 调用方法
  30.         // 调用方法有几个要素?也需要4要素。
  31.         // 反射机制中最最最最最重要的一个方法,必须记住。
  32.         /*
  33.             四要素:
  34.             loginMethod方法
  35.             obj对象
  36.             "admin","123" 实参
  37.             retValue 返回值
  38.          */
  39.         Object resValues = loginMethod.invoke(obj, "admin", "123");//注:方法返回值是void 结果是null
  40.         System.out.println(resValues);
  41.     }
  42. }
复制代码
反射Constructor【反射/反编译一个类的构造方法】Constructor类方法
public String getName() 返回构造方法名
public int getModifiers() 获取构造方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?>[] getParameterTypes() 返回构造方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public T newInstance(Object … initargs) 创建对象【参数为创建对象的数据】
通过反射机制调用构造方法实例化java对象
  1. /*

  2. 通过反射机制调用构造方法实例化java对象。(这个不是重点)
  3. */
  4. class ReflectTest12{
  5.     public static void main(String[] args) throws Exception {
  6.         //不使用反射创建对象
  7.         Vip vip1 = new Vip();
  8.         Vip vip2 = new Vip(123, "zhangsan", "2001-10-19", false);

  9.         //使用反射机制创建对象(以前)
  10.         Class vipClass = Class.forName("javase.reflectBean.Vip");
  11.         // 调用无参数构造方法
  12.         Object obj1 = vipClass.newInstance();//Class类的newInstance方法
  13.         System.out.println(obj1);

  14.         //使用反射机制创建对象(现在)
  15.         // 调用有参数的构造方法怎么办?
  16.         // 第一步:先获取到这个有参数的构造方法
  17.         Constructor c1 = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);//参数列表:Class对象的数组,这些对象按声明的顺序标识构造函数的形式参数类型
  18.         // 第二步:调用构造方法new对象
  19.         Object obj2 = c1.newInstance(321, "lsi", "1999-10-11", true);//Constructor类的newInstance方法
  20.         System.out.println(obj2);

  21.         // 获取无参数构造方法
  22.         Constructor c2 = vipClass.getDeclaredConstructor();
  23.         Object obj3 = c2.newInstance();
  24.         System.out.println(obj3);
  25.     }
  26. }
复制代码
如果需要调用无参构造方法,getDeclaredConstructor()方法形参为空即可
获取一个类的父类以及实现的接口
两个方法【Class类中的】
public native Class<? super T> getSuperclass()
public Class<?>[] getInterfaces()
  1. /*
  2. 重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
  3. */
  4. class ReflectTest13{
  5.     public static void main(String[] args) throws Exception{
  6.         // String举例
  7.         Class vipClass = Class.forName("java.lang.String");
  8.         // 获取String的父类
  9.         Class superclass = vipClass.getSuperclass();
  10.         // 获取String类实现的所有接口(一个类可以实现多个接口。)
  11.         Class[] interfaces = vipClass.getInterfaces();
  12.         System.out.println(superclass.getName());
  13.         for (Class i : interfaces) {
  14.             System.out.println(i.getName());
  15.         }
  16.     }
  17. }
复制代码
注意
  • 属性最重要的是名字
  • 实例方法最重要的是名字形参列表
  • 构造方法最重要的是形参列表

Class.forName(classname) 获取classname类中的所有属性包括类名
Class.newInstance()实例化对象,并触发该类的构造方法
Class.getMethod(method name,arg) 获取一个对象中的public方法,由于java支持方法的重载,所以需要第二参数作为获取的方法的形参列表,这样就可以确定获取的是哪一个方法。还记得重载是什么吗?子类对父类中允许访问的方法进行重新编写,返回值和形参不能改变。
Method.invoke() 执行方法,如果是一个普通方法,则invoke的第一个参数为该方法所在的对象,如果是静态方法则第一个参数是null或者该方法所在的类 第二个参数为要执行方法的参数。
obj.getClass() 如果上下文中存在某个类的实例obj,那我们可以直接通过obj.getClass来获取它的类
Y1.class 如果已经加载了一个类Y1,只是想获取到它由java.lang.class所创造的对象,那么就直接使用这种方法获取即可,这种方法并不属于反射
Class.Forname 如果知道某个类的名字,想获取到这个类,就可以使用forName来获取
其他问题形参和实参
java方法可以是无参的,也可以是有参的,而参数又分为了形参和实参。
形参:是指在定义函数时使用的参数,目的是用于接收调用该函数时传入的参数。简单理解,就是所有函数(即方法)的参数都是形参。
实参:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
变量赋值的方式
如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
  1. public class ValueTransferTest {

  2.     public static void main(String[] args) {

  3.         System.out.println("***********基本数据类型:****************");
  4.         int m = 10;
  5.         int n = m;

  6.         System.out.println("m = " + m + ", n = " + n);

  7.         n = 20;

  8.         System.out.println("m = " + m + ", n = " + n);

  9.         System.out.println("***********引用数据类型:****************");

  10.         Order o1 = new Order();
  11.         o1.orderId = 1001;

  12.         Order o2 = o1;//赋值以后,o1和o2的地址值相同,都指向了堆空间中同一个对象实体。

  13.         System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);

  14.         o2.orderId = 1002;

  15.         System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);

  16.     }

  17. }

  18. class Order{

  19.     int orderId;
  20.     }
  21. 运行结果:
  22. ***********基本数据类型:****************
  23. m = 10, n = 10
  24. m = 10, n = 20
  25. ***********引用数据类型:****************
  26. o1.orderId = 1001,o2.orderId = 1001
  27. o1.orderId = 1002,o2.orderId = 1002
复制代码
在Java中,对象变量存储的是对象的引用,而不是对象本身。在这个程序中,创建了两个Order对象o1和o2,并将o2指向了o1,即o2和o1引用了同一个对象。因此,当o2.orderId被赋值为1002时,也会同时改变o1.orderId的值,因为它们都引用了同一个对象,对这个对象的修改会影响所有引用它的变量。
值传递机制
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
  1. public class ValueTransferTest1 {
  2.     public static void main(String[] args) {

  3.         int m = 10;
  4.         int n = 20;

  5.         System.out.println("m = " + m + ", n = " + n);
  6.         //交换两个变量的值的操作
  7. //      int temp = m ;
  8. //      m = n;
  9. //      n = temp;

  10.         ValueTransferTest1 test = new ValueTransferTest1();
  11.         test.swap(m, n);

  12.         System.out.println("m = " + m + ", n = " + n);


  13.     }


  14.     public void swap(int m,int n){
  15.         int temp = m ;
  16.         m = n;
  17.         n = temp;
  18.     }
  19. }
复制代码
这里交换失败了
交换参数失败的原因是 Java 中的参数传递方式是值传递,即将参数值复制一份传递给方法,因此在 swap 方法中修改 m 和 n 的值并不会影响到 main 方法中的 m 和 n 的值,也就是说 swap 方法中的交换操作只是在其局部变量中完成的。所以,最终输出的结果与交换操作前是一样的。
如果想要在方法中修改传递进来的参数,可以通过将参数定义为一个对象(例如数组或类的实例)的方式来实现。或者也可以使用返回值来传递方法内修改后的值。
再来看个成功的
  1. public class ValueTransferTest2 {

  2.     public static void main(String[] args) {

  3.         Data data = new Data();

  4.         data.m = 10;
  5.         data.n = 20;

  6.         System.out.println("m = " + data.m + ", n = " + data.n);

  7.         //交换m和n的值
  8. //      int temp = data.m;
  9. //      data.m = data.n;
  10. //      data.n = temp;

  11.         ValueTransferTest2 test = new ValueTransferTest2();
  12.         test.swap(data);


  13.         System.out.println("m = " + data.m + ", n = " + data.n);

  14.     }

  15.     public void swap(Data data){
  16.         int temp = data.m;
  17.         data.m = data.n;
  18.         data.n = temp;
  19.     }

  20. }

  21. class Data{

  22.     int m;
  23.     int n;

  24. }
  25. 输出
  26. m = 10, n = 20
  27. m = 20, n = 10
复制代码
在这个程序中,交换参数成功的原因是传递的参数是一个对象,而对象是引用类型,在 Java 中,引用类型变量存储的是对象的地址,也就是说,当对象传递给方法时,传递的是对象的地址,也就是引用,方法内部对对象引用的操作会影响到对象本身。
在 swap 方法中,参数 data 是一个对象的引用,通过操作该对象的属性 m 和 n 来实现参数的交换。因为 data 是一个对象的引用,所以 swap 方法内部修改 data 引用所指向的对象的属性时,会直接修改传递进来的对象 data 的属性,也就实现了参数的交换。
因此,交换参数成功的前提是传递的参数是对象或数组等引用类型变量,而不是基本数据类型
来源:https://xz.aliyun.com/  感谢【yuuuu】



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 09:39 , Processed in 0.013665 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表