欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一篇文章全面了解Java反射机制

程序员文章站 2022-05-11 07:57:40
Java的反射机制在实践中可谓无处不在,如果你已经工作几年,还对Java的反射机制一知半解,那么这篇文章绝对值得你读一读。 什么是反射 反射 (Reflection) 是Java的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。 通俗的来讲就是:通过反射机制,可以 ......

java的反射机制在实践中可谓无处不在,如果你已经工作几年,还对java的反射机制一知半解,那么这篇文章绝对值得你读一读。

什么是反射

反射 (reflection) 是java的特征之一,它允许运行中的java程序获取自身的信息,并且可以操作类或对象的内部属性。

通俗的来讲就是:通过反射机制,可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。

注意这里的重点是:运行时,而不是编译时。我们常规情况下写的对象类型都是在编译期就确定下来的。而java反射机制可以动态地创建对象并调用其属性,这样创建对象的方式便异常灵活了。

虽然通过反射可以动态的创建对象,增加了灵活性,但也不是什么地方都可用,还要考虑性能、编码量、安全、面向对象性等。

我们知道java是面向对象的,如果通过反射机制去操作对象里面的属性或方法,一定程度上破坏了面向对象的特性。同时,通过反射机制还可以修改私有变量,也存在一定的安全性问题。

但这并不影响反射在实践中的应用,几乎各大框架多多少少都在使用java反射机制。特别是主流的spring框架。

功能及用途

java反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

反射最重要的用途之一就是开发各类通用框架。以spring为例,当基于xml进行配置bean时,我们通常写如下代码:

<bean class="com.choupangxia.userserviceimpl">
</bean>

spring在启动的时候便会利用反射机制去加载对应的userserviceimpl类,然后进行实例化。如果不存在该类则会抛出异常,通常异常中还会出现invoke方法调用的堆栈信息。

当spring基于注解去实例化对象时,同样利用的是反射机制。下面通过一个简单demo示例,演示一下如何通过反射获得注解信息:

static void inituser(user user) throws illegalaccessexception {

	// 获取user类中所有的属性(getfields无法获得private属性)
	field[] fields = user.class.getdeclaredfields();

	// 遍历所有属性
	for (field field : fields) {
		// 如果属性上有此注解,则进行赋值操作
		if (field.isannotationpresent(initsex.class)) {
			initsex init = field.getannotation(initsex.class);
			field.setaccessible(true);
			// 设置属性的性别值
			field.set(user, init.sex().tostring());
			system.out.println("完成属性值的修改,修改值为:" + init.sex().tostring());
		}
	}
}

上述代码是之前写《一篇文章,全面了解java自定义注解》中的示例,相关文章可关注公众号“程序新视界”,回复“注解”获得全文。

更多关于java反射的例子我们就不多说了。上面的示例现在看不懂也没关系,下面我们就来详细介绍一下java反射机制的具体使用。

简单示例

我们通过一个简单的示例对比,来了解一下java反射机制。首先来看正常情况下创建对象并使用对象的示例:

user user = new user();
user.setusername("公众号:程序新视界");
user.setage(3);

那么,当基于反射机制来达到统一效果该怎么做呢?看下面的具体实现:

@test
public void testcreatereflect() throws classnotfoundexception, nosuchmethodexception, illegalaccessexception,
		invocationtargetexception, instantiationexception {
	// 获取user所对应的class对象
	class clz = class.forname("com.choupangxia.reflect.user");
	// 获取user类无参数的构造器
	constructor constructor = clz.getconstructor();
	// 通过构造器创建user对象
	user user = (user) constructor.newinstance();
	user.setusername("公众号:程序新视界");
	user.setage(3);
	system.out.println("username=" + user.getusername());
	system.out.println("age=" + user.getage());
}

在上述过程中通过class.forname获得user所对应的class对象,获得构造器constructor,通过构造器创建出来一个user对象,然后调用对应的方法。

当然,后面的步骤中也可以完全不出现user类,直接通过class对象获得对应的method进行调用。示例如下:

method setusernamemethod = clz.getmethod("setusername", string.class);
method setagemethod = clz.getmethod("setage", int.class);

setusernamemethod.invoke(obj,"公众号:程序新视界");
setagemethod.invoke(obj,3);

关于get方法也是如此操作,就不再赘述。

经过上面的实例我们已经能够正常创建对象,并使用对象了。下面就看看反射常用的api,通过这些api我们可以实现更多的更复杂的功能。

反射常用api

获取class对象的三种方法

第一种方法:当你知道类的全路径名时,可使用class.forname静态方法来获得class对象。上面的示例就是通过这种方法获得:

class clz = class.forname("com.choupangxia.reflect.user");

第二种方法:通过“.class”获得。前提条件是在编译前就能够拿到对应的类。

class clz = user.class;

第三种:使用类对象的getclass()方法。

user user = new user();
class clz = user.getclass();

创建对象的两种方法

可以通过class对象的newinstance()方法和通过constructor 对象的newinstance()方法创建类的实例对象。

第一种:通过class对象的newinstance()方法。

class clz = user.class;
user user = (user) clz.newinstance();

第二种:通过constructor对象的newinstance()方法。

class clz = class.forname("com.choupangxia.reflect.user");
constructor constructor = clz.getconstructor();
user user = (user) constructor.newinstance();

其中第二种方法创建类对象可以选择特定构造方法,而通过 class对象则只能使用默认的无参数构造方法。

class clz = user.class;
constructor constructor = clz.getconstructor(string.class);
user user = (user) constructor.newinstance("公众号:程序新视界");

获取类属性、方法、构造器

通过class对象的getfields()方法获取非私有属性。

field[] fields = clz.getfields();
for(field field : fields){
	system.out.println(field.getname());
}

上述实例中的user对象属性都是private,无法直接通过上述方法获取,可将其中一个属性改为public,即可获取。

通过class对象的getdeclaredfields()方法获取所有属性。

field[] fields = clz.getdeclaredfields();
for(field field : fields){
	system.out.println(field.getname());
}

执行打印结果:

username
age

当然针对属性的其他值也是可以获取的,针对私有属性的修改需要先调用field.setaccessible(true)方法,然后再进行赋值。关于具体应用,回头看我们最开始关于注解的实例中的使用。

获取方法的示例如下:

method[] methods = clz.getmethods();
for(method method : methods){
	system.out.println(method.getname());
}

打印结果:

setusername
setage
getusername
getage
wait
wait
wait
equals
tostring
hashcode
getclass
notify
notifyall

可以看到,不仅获取到了当前类的方法,还获取到了该类父类object类中定义的方法。

关于获取构造器的方法上面已经讲到了,就不再赘述。而上述的这些方法在class中都有相应的重载的方法,可根据具体情况进行灵活使用。

利用反射创建数组

最后,我们再看一个通过反射创建数组的实例。

@test
public void createarray() throws classnotfoundexception {
	class<?> cls = class.forname("java.lang.string");
	object array = array.newinstance(cls,5);
	// 向数组添加内容
	array.set(array,0,"hello");
	array.set(array,1,"公众号");
	array.set(array,2,"程序新视界");
	array.set(array,3,"二师兄");
	array.set(array,4,"java");
	// 获取数组中指定位置的内容
	system.out.println(array.get(array,2));
}

小结

想必经过上述的学习,对java反射机制有了更进一步的了解,在最开始我们已经说了反射机制也是有不足的。因此,如果可能尽量使用正统的写法,但如果你在开发通用框架,则可考虑使用。

后面,有机会我们再讲解反射机制的源码,别忘记关注公众号:程序新视界。


程序新视界:精彩和成长都不容错过
![程序新视界-微信公众号](https://img2020.cnblogs.com/other/1742867/202003/1742867-20200326074258111-1265370724.png)