衡阳信安 2023-09-11 00:00 发表于湖南 上次的java反射没怎么懂,再来学习一下。 概念Java反射是指在程序运行期间对于类的属性、方法、构造方法等进行动态访问和操作的机制。通过Java反射API,程序能够在运行期获取类的信息,操作类的属性、方法、构造方法,创建类的对象等。 不适用new创建对象而访问类中方法的过程即可成为反射 包java反射的相关类位于java.lang.reflect.*;包 为什么要用反射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(“完整类名带包名”)
- Class<?> cls = Class.forName("java.util.ArrayList");
复制代码 对象.getClass()
- Object obj = new String("Hello World");
- Class<? extends Object> objClass = obj.getClass();
复制代码 类名.class
- 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对象 通过反射实例化对象
没有公共的无参构造函数,newInstance()方法就会抛出InstantiationException异常 其他方法 使用getDeclaredConstructor()方法获取特定的构造函数,并使用newInstance(Object... args)方法传递参数来创建对象实例 - Class<MyClass> cls = MyClass.class;
- Constructor<MyClass> constructor = cls.getDeclaredConstructor(int.class, String.class);
- MyClass myObj = constructor.newInstance(42, "Hello");
复制代码 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) 读取属性值 给属性赋值和读直接赋值 通过反射赋值 - 属性.set(对象, 值);
- 属性.get(对象);
复制代码- /*
- 必须掌握:
- 怎么通过反射机制访问一个java对象的属性?
- 给属性赋值set
- 获取属性的值get
- */
- class ReflectTest07{
- public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
- //不使用反射机制给属性赋值
- Student student = new Student();
- /**给属性赋值三要素:给s对象的no属性赋值1111
- * 要素1:对象s
- * 要素2:no属性
- * 要素3:1111
- */
- student.no = 1111;
- /**读属性值两个要素:获取s对象的no属性的值。
- * 要素1:对象s
- * 要素2:no属性
- */
- System.out.println(student.no);
- //使用反射机制给属性赋值
- Class studentClass = Class.forName("javase.reflectBean.Student");//首先获取Class类
- Object obj = studentClass.newInstance();//实例化为对象obj,obj就是Student对象。(底层调用无参数构造方法)
- // 获取no属性(根据属性的名称来获取Field)
- Field noField = studentClass.getDeclaredField("no");
- // 给obj对象(Student对象)的no属性赋值
- /*
- 虽然使用了反射机制,但是三要素还是缺一不可:
- 要素1:obj对象
- 要素2:no属性
- 要素3:22222值
- 注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
- */
- noField.set(obj, 22222);
- // 读取属性的值
- // 两个要素:获取obj对象的no属性的值。
- System.out.println(noField.get(obj));
- }
复制代码
反射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) 调用方法 - /*
- 重点:必须掌握,通过反射机制怎么调用一个对象的方法?
- 五颗星*****
- 反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,
- 将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,
- 但是java代码不需要做任何改动。这就是反射机制的魅力。
- */
- class ReflectTest10{
- public static void main(String[] args) throws Exception {
- // 不使用反射机制,怎么调用方法
- // 创建对象
- UserService userService = new UserService();
- // 调用方法
- /*
- 要素分析:
- 要素1:对象userService
- 要素2:login方法名
- 要素3:实参列表
- 要素4:返回值
- */
- System.out.println(userService.login("admin", "123") ? "登入成功!" : "登入失败!");
- //使用反射机制调用方法
- Class userServiceClass = Class.forName("javase.reflectBean.UserService");
- // 创建对象
- Object obj = userServiceClass.newInstance();
- // 获取Method
- Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
- // Method loginMethod = userServiceClass.getDeclaredMethod("login");//注:没有形参就不传
- // 调用方法
- // 调用方法有几个要素?也需要4要素。
- // 反射机制中最最最最最重要的一个方法,必须记住。
- /*
- 四要素:
- loginMethod方法
- obj对象
- "admin","123" 实参
- retValue 返回值
- */
- Object resValues = loginMethod.invoke(obj, "admin", "123");//注:方法返回值是void 结果是null
- System.out.println(resValues);
- }
- }
复制代码 反射Constructor【反射/反编译一个类的构造方法】Constructor类方法public String getName() 返回构造方法名
public int getModifiers() 获取构造方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class<?>[] getParameterTypes() 返回构造方法的修饰符列表(一个方法的参数可能会有多个。)【结果集一般配合Class类的getSimpleName()方法使用】
public T newInstance(Object … initargs) 创建对象【参数为创建对象的数据】 通过反射机制调用构造方法实例化java对象- /*
- 通过反射机制调用构造方法实例化java对象。(这个不是重点)
- */
- class ReflectTest12{
- public static void main(String[] args) throws Exception {
- //不使用反射创建对象
- Vip vip1 = new Vip();
- Vip vip2 = new Vip(123, "zhangsan", "2001-10-19", false);
- //使用反射机制创建对象(以前)
- Class vipClass = Class.forName("javase.reflectBean.Vip");
- // 调用无参数构造方法
- Object obj1 = vipClass.newInstance();//Class类的newInstance方法
- System.out.println(obj1);
- //使用反射机制创建对象(现在)
- // 调用有参数的构造方法怎么办?
- // 第一步:先获取到这个有参数的构造方法
- Constructor c1 = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);//参数列表:Class对象的数组,这些对象按声明的顺序标识构造函数的形式参数类型
- // 第二步:调用构造方法new对象
- Object obj2 = c1.newInstance(321, "lsi", "1999-10-11", true);//Constructor类的newInstance方法
- System.out.println(obj2);
- // 获取无参数构造方法
- Constructor c2 = vipClass.getDeclaredConstructor();
- Object obj3 = c2.newInstance();
- System.out.println(obj3);
- }
- }
复制代码如果需要调用无参构造方法,getDeclaredConstructor()方法形参为空即可 获取一个类的父类以及实现的接口两个方法【Class类中的】 public native Class<? super T> getSuperclass()
public Class<?>[] getInterfaces() - /*
- 重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
- */
- class ReflectTest13{
- public static void main(String[] args) throws Exception{
- // String举例
- Class vipClass = Class.forName("java.lang.String");
- // 获取String的父类
- Class superclass = vipClass.getSuperclass();
- // 获取String类实现的所有接口(一个类可以实现多个接口。)
- Class[] interfaces = vipClass.getInterfaces();
- System.out.println(superclass.getName());
- for (Class i : interfaces) {
- System.out.println(i.getName());
- }
- }
- }
复制代码 注意属性最重要的是名字 实例方法最重要的是名字和形参列表 构造方法最重要的是形参列表
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方法可以是无参的,也可以是有参的,而参数又分为了形参和实参。 形参:是指在定义函数时使用的参数,目的是用于接收调用该函数时传入的参数。简单理解,就是所有函数(即方法)的参数都是形参。 实参:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。 变量赋值的方式如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。 - public class ValueTransferTest {
- public static void main(String[] args) {
- System.out.println("***********基本数据类型:****************");
- int m = 10;
- int n = m;
- System.out.println("m = " + m + ", n = " + n);
- n = 20;
- System.out.println("m = " + m + ", n = " + n);
- System.out.println("***********引用数据类型:****************");
- Order o1 = new Order();
- o1.orderId = 1001;
- Order o2 = o1;//赋值以后,o1和o2的地址值相同,都指向了堆空间中同一个对象实体。
- System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);
- o2.orderId = 1002;
- System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);
- }
- }
- class Order{
- int orderId;
- }
- 运行结果:
- ***********基本数据类型:****************
- m = 10, n = 10
- m = 10, n = 20
- ***********引用数据类型:****************
- o1.orderId = 1001,o2.orderId = 1001
- o1.orderId = 1002,o2.orderId = 1002
复制代码在Java中,对象变量存储的是对象的引用,而不是对象本身。在这个程序中,创建了两个Order对象o1和o2,并将o2指向了o1,即o2和o1引用了同一个对象。因此,当o2.orderId被赋值为1002时,也会同时改变o1.orderId的值,因为它们都引用了同一个对象,对这个对象的修改会影响所有引用它的变量。 值传递机制如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。 - public class ValueTransferTest1 {
- public static void main(String[] args) {
- int m = 10;
- int n = 20;
- System.out.println("m = " + m + ", n = " + n);
- //交换两个变量的值的操作
- // int temp = m ;
- // m = n;
- // n = temp;
- ValueTransferTest1 test = new ValueTransferTest1();
- test.swap(m, n);
- System.out.println("m = " + m + ", n = " + n);
- }
- public void swap(int m,int n){
- int temp = m ;
- m = n;
- n = temp;
- }
- }
复制代码这里交换失败了 交换参数失败的原因是 Java 中的参数传递方式是值传递,即将参数值复制一份传递给方法,因此在 swap 方法中修改 m 和 n 的值并不会影响到 main 方法中的 m 和 n 的值,也就是说 swap 方法中的交换操作只是在其局部变量中完成的。所以,最终输出的结果与交换操作前是一样的。 如果想要在方法中修改传递进来的参数,可以通过将参数定义为一个对象(例如数组或类的实例)的方式来实现。或者也可以使用返回值来传递方法内修改后的值。 再来看个成功的 - public class ValueTransferTest2 {
- public static void main(String[] args) {
- Data data = new Data();
- data.m = 10;
- data.n = 20;
- System.out.println("m = " + data.m + ", n = " + data.n);
- //交换m和n的值
- // int temp = data.m;
- // data.m = data.n;
- // data.n = temp;
- ValueTransferTest2 test = new ValueTransferTest2();
- test.swap(data);
- System.out.println("m = " + data.m + ", n = " + data.n);
- }
- public void swap(Data data){
- int temp = data.m;
- data.m = data.n;
- data.n = temp;
- }
- }
- class Data{
- int m;
- int n;
- }
- 输出
- m = 10, n = 20
- m = 20, n = 10
复制代码在这个程序中,交换参数成功的原因是传递的参数是一个对象,而对象是引用类型,在 Java 中,引用类型变量存储的是对象的地址,也就是说,当对象传递给方法时,传递的是对象的地址,也就是引用,方法内部对对象引用的操作会影响到对象本身。 在 swap 方法中,参数 data 是一个对象的引用,通过操作该对象的属性 m 和 n 来实现参数的交换。因为 data 是一个对象的引用,所以 swap 方法内部修改 data 引用所指向的对象的属性时,会直接修改传递进来的对象 data 的属性,也就实现了参数的交换。 因此,交换参数成功的前提是传递的参数是对象或数组等引用类型变量,而不是基本数据类型
|