Java反射:框架设计的灵魂,关于Java反射的思考

本文深入解析Java反射机制,介绍如何在运行时分析类、获取对象属性、执行方法及构造器,探讨反射在框架设计中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是反射

    说起框架,相信很多人都用过,如今的开发基本离不开框架,从spring,strtus等到如今的spring boot,框架已经深入到了开发的点点滴滴,相信很多人在使用spring框架时都是这样使用的,在xml文件中配置相应的Java类和方法等,这样的话使用注解就不需要自己的new一个对象了,spring框架已经帮我们处理好了,这就是反射。

反射就是运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

反射机制可以用来:
•在运行时分析类的能力。
•在运行时查看对象,例如,编写一个toString方法供所有类使用
•实现通用的数组操作代码。
•利用Method对象,这个对象很像C++中的函数指针。
反射是一种功能强大且复杂的机制。

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类,虚拟机利用运行时类型信息选择相应的方法执行,可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class,一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如, int不是类,但int.class是一个Class类型的对象。Class类实际上是一个泛型类,我们都知道Java程序编译后会编译成一个个的class文件,这个class文件实际上就相当于一个个的Class.

利用反射得到类的属性

在java.lang.reflect包中有3个类Field、Method和Constructor分別用于描述类的域、方 法和构造器-这三个类都有一个叫getName的方法,用來返冋项目的名称。Field类有一 个getType方法,用来返回描述域所属类型的Class对象 Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返冋类型的方法。

Class类屮的getFields、getMethods和getConstruc tors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFields. getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。

通过反射得到相应的Java类属性:

  1. 通过反射得到Java构造方法:
  2. 首先添加一个Java类
package reflect;

public class reflectTest1 {
	public reflectTest1() {
		System.out.println("无参构造方法");
	}

	public reflectTest1(int i) {
		System.out.println("参数为整数类型构造方法");
	}

	public reflectTest1(int i, String a) {
		System.out.println("参数为多个的构造方法");
	}
}

    3. 通过反射得到相应的构造方法:

    @Test
	public void test1() throws Exception {
		// 根据包名.类名得到Class字节码对象
		Class cl = Class.forName("reflect.reflectTest1");
		// 得到类的无参构造方法
		Constructor constructor = cl.getConstructor(null);
		// 创建对象,相当于new
		constructor.newInstance(null);
		// 得到一个参数的构造方法
		Constructor constructor2 = cl.getConstructor(int.class);
		// 创建对象
		constructor2.newInstance(1);
		// 得到多个参数的构造方法
		Constructor constructor3 = cl.getConstructor(int.class, String.class);
		// 创建对象
		constructor3.newInstance(1, "test");
	}

结果如图所示:

如果该构造方法是私有的构造方法,那么就需要强制反射,这个时候使用的是另一个方法

Constructor constructor4 = cl.getDeclaredConstructor(String.class);
// 私有的构造方法强制反射
constructor4.setAccessible(true);
// 创建对象
constructor4.newInstance("test");

通过反射执行Java方法,首先定义几个方法:

    public void test1() {
 		System.out.println("test1.....");
	}

	public void test2(String val) {
		System.out.println("test2....." + val);
	}

	public static void test3(String v, String u) {
		System.out.println("test3....." + v + "......" + u);
	}

通过反射执行相应的方法:

      @Test
	public void test2() throws Exception {
		// 根据包名.类名得到Class字节码对象
		Class cl = Class.forName("reflect.reflectTest1");
		// 得到类的无参构造方法
		Constructor constructor = cl.getConstructor(null);
		// 创建对象
		Object obj = cl.newInstance();
		// 得到test1方法,第一个参数为方法名,第二个参数为参数类型
		Method method = cl.getMethod("test1", null);
		// 执行方法,第一个参数为class对象,第二个为方法的参数
		method.invoke(obj, null);

		Method method2 = cl.getMethod("test2", String.class);
		method2.invoke(obj, "test");
		Method method3 = cl.getMethod("test3", String.class, String.class);
		method3.invoke(null, "test", "123");
	}

同样的,如果该方法是私有的,那么必须要使用

    Method m = cl.getDeclaredMethod(“方法名”,参数类型);

    m.setAccessible(true);

特别提出:如果方法的参数是数组,不可以直接在调用的传输数组进去,那样的话会出现参数类型不一致的错误,解决方法是:

        // 如果参数是数组类型,正确写法
        Method method4 = cl.getMethod("test4", String[].class);
        method4.invoke(obj, new Object[] { new String[] { "a", "b" } });
        // 如果参数是数组类型,错误写法
        Method method5 = cl.getMethod("test4", String[].class);
        method5.invoke(obj, new String[] { "a", "b" });

如果按照错误写法,会出现错误参数类型不一致。

java.lang.IllegalArgumentException: wrong number of arguments
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
原因就是jdk1.4和jdk1.5处理invoke方法的问题,但是就算使用的是比1.4更高的版本,为了向下兼容,这种写法也是错误的,,因为在jdk1.5中,整个数组被当成一个参数,而在jdk1.4的源码中,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会把数组拆分,这样就不是一个参数,而是多个参数,就会出现参数不一致的问题,所以,需要将String数组放入一个object数组中,这样整个的string数组就是一个参数。

除此之外,还有一种方法,就是将string类型的数组转成object类型,即:method5.invoke(obj, (Object) new String[] { "a", "b" });

通过反射得到Java 类的成员变量

    private String name = "reflect";
    public int age = 23;
    private static final Boolean isStudent = true;

@Test
	public void test3() throws Exception {
		// 根据包名.类名得到Class字节码对象
		Class cl = Class.forName("reflect.reflectTest1");
		// 创建对象
		Object obj = cl.newInstance();
		// 成员变量为私有
		Field nameField = cl.getDeclaredField("name");
		nameField.setAccessible(true);
		System.out.println(nameField.get(obj));
		// 改变成员变量的值
		nameField.set(obj, "changeReflect");
		System.out.println(nameField.get(obj));

		// public
		Field ageField = cl.getField("age");
		System.out.println(ageField.get(obj));

		Field isStudentField = cl.getDeclaredField("isStudent");
		isStudentField.setAccessible(true);
		System.out.println(isStudentField.get(obj));
	}

运行结果:

除此之外,还有批量获取成员变量,批量获取类方法等方法,具体可查看JavaAPI文档

看到这里,相信你对框架的设计有了更深的理解,明白了为什么框架需要配置Java类,Java方法,其实就是这个道理,通过配置文件实现了Java的反射,而用户自己则 不必建里太多的对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值