书写代码必须符合高质量高性能要求,这也是能够在视觉上和其他程序员拉开差距的技能,同时也是一个优秀程序员的基本要求。
今天我们说一说反射,反射不是设计模式,但是反射机制作为java的基础之一,在众多框架的源码中大量使用,是很多设计模式,框架,组件的重要基础。比如我们知道的spring aop底层是jdk的动态代理,而动态代理依赖的就是反射机制。
在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象都能调用它的任意方法和属性,这种动态获取类信息以及动态调用对象方法的功能称为Java的发射机制。
先了解下类对象的概念:
我们知道类的加载过程为加载 验证 准备 解析 初始化 销毁,在加载阶段,jvm会根据类的全限定名找到二进制字节流,并把这个二进制字节流加载进内存,转为运行时数据区的存储结构,最后创建一个java.lang.Class类型的实例作为方法区这个类型的访问入口,这里所说的Class实例就是类对象,这个类对象创建完成后会存放在堆区,这个动作是在加载阶段完成。
方法区中存放的是类的元数据,包括静态变量,有哪些属性,有哪些方法,继承的父类,实现的接口,异常相关的信息等等,而类对象就是这些信息的访问入口,Class类提供了很多api,这些api大多是native方法,也就说明这个类对象只是这个类在堆区的一个接口,由jvm底层来实现,jvm底层会根据每个api的功能去方法区拿类的信息。
public static void main(String[] args) { UserService userService = new UserService(); System.out.println("new关键字创建对象:"+userService); Class<UserService> userClass= UserService.class; Class userClass1= Class.forName("UserService"); Constructor<?>[] constructors=userClass.getDeclaredConstructors(); Constructor constructor=constructors[0]; Object user=constructor.newInstance("333333","666666"); System.out.println("反射创建对象:"+user;}
通过上面的这个例子,我们可以看到反射是如何应用的:
以上是利用反射机制创建对象,当然除了创建对象,还可以获取类的属性实例Field和方法实例Method,通过方法实例和属性实例的api对对象的方法和属性进行设置或者执行。这便是反射的应用。
new关键字创建对象是加载类完成后接着走创建对象过程,而反射过程是Class.forName()触发加载类,但是不会创建对象,只有在调用newInstance方法时候才会创建对象,也就是把加载类和创建对象分为两个部分完成,但是调用newInstance方法创建前必须保证类已经加载完成。
new关键字创建对象是静态编译,而反射创建对象是动态编译:
比如下面的代码,编译阶段是不知道是否要创建UserService类的对象的,所以UserService不会被加载:
public void reflex(String str) { if("UserService".equals(str)){ Class userClass= Class.forName("UserService"); Constructor<?>[] constructors=userClass.getDeclaredConstructors(); Constructor constructor=constructors[0]; Object UserService=constructor.newInstance("333333","666666"); System.out.println(UserService.toString()); } }
以java8为讨论基础,网上所说的反射只能通过无参构造方法创建对象是不正确的,事实证明,反射不仅仅可以通过有参构造方式创建对象,而且还可以通过私有构造方法创建对象,而且这种通过私有构造方法创建对象的方式会破坏单例模式,你想一下,单例模式中的构造方法之所以是私有就是为了不允许外部创建单例对象。而通过反射可以创建的话,那不是违背了单例模式的定理吗。
例:只是为了说明反射,所有代码中的单例只是一个简单的饿汉式单例:
public class IdGenerator { private String k; private static final IdGenerator instance = new IdGenerator(); private IdGenerator(String k) { this.k=k; } public static IdGenerator getInstance() { return instance; } } public class reflex { public static void main(String[] args){ Class idGeneratorClass= Class.forName("IdGenerator"); Constructor<?>[] constructors=idGeneratorClass.getDeclaredConstructors(); Constructor constructor=constructors[0]; constructor.setAccessible(true);//暴力反射,可以突破私有权限 Object idGenerator=constructor.newInstance("333333"); System.out.println(idGenerator==IdGenerator.getInstance()); }}
这个例子既验证了反射调用有参构造方法创建实例,又验证了反射破坏单例模式。
反射会造成泛型擦除:
List<UserService> list=new ArrayList<UserService>(); list.add(new UserService()); list.add(new UserService()); list.add(new UserService()); Class<? extends List> listClass=list.getClass(); Method method=listClass.getDeclaredMethod("add",Object.class); method.invoke(list,"123"); for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); }
上面的例子中通过反射创建的list对象,在调用add方法的时候不会限制类型,导致无法用某个类型去接收list集合中数据,否则会报类型转换异常,这种情况只能直接返回前端。
效率问题 ,反射的效率比new字段创建对象的效率低很多,因此在使用的时候要特别注意性能问题,但是即便是这样,我们写出的代码主要的性能影响点很少是反射造成,而大多情况是因为代码结构框架,函数,工具,底层原理的不合理使用造成的。因此发射机制可以用在代码中,但是要用在合适的位置。
现在来总结下反射的作用:
可以在程序运行过程中去操作字节码文件和类对象进而进行得到类信息,创建对象以及执行对象方法等,不需要重新编译,提高程序的扩展性 复用性 解耦
反射相关的四个类,这些类的中的方法底层大多是native方法,所以反射其实是jvm底层实现。掌握了这四个类,灵活运用,基本就掌握了反射机制。
field是类中的成员变量:变量和属性是俩个概念,变量有get和set方法就是属性。
newInstance(object arg...) 传入参数的时候,会调用有对应参数的构造方法创建对象,不传参数就是使用默认构造方法。
setAccessible() 暴力反射,忽略访问权限修饰符:
Class userClass= Class.forName("UserService");Constructor<?>[] constructors=userClass.getDeclaredConstructors();Constructor constructor=constructors[0];Object UserService=constructor.newInstance("333333","666666");
所谓暴力反射,就是当类中有私有构造方法,私有属性,私有方法的时候,对这些对象进行反射调用的时候会报错,原因是无法突破私有权限,反射调用前先调用对象的setAccessible方法,设置为true,就可以突破私有权限,代码可以看上面破坏单例的例子。
本文链接:http://www.28at.com/showinfo-26-16379-0.html你真的了解Java的反射机制吗?
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
下一篇: Go 使用环境变量