书写代码必须符合高质量高性能要求,这也是能够在视觉上和其他程序员拉开差距的技能,同时也是一个优秀程序员的基本要求。
- 何为高质量:代码具备可维护性,可读性,可扩展性,灵活性,简洁性,可复用性, 可测试性。
- 何为高性能:代码能尽可能的提高处理效率。
今天我们说一说反射,反射不是设计模式,但是反射机制作为java的基础之一,在众多框架的源码中大量使用,是很多设计模式,框架,组件的重要基础。比如我们知道的spring aop底层是jdk的动态代理,而动态代理依赖的就是反射机制。
一、反射机制
1.概念
在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象都能调用它的任意方法和属性,这种动态获取类信息以及动态调用对象方法的功能称为Java的发射机制。
先了解下类对象的概念:
我们知道类的加载过程为加载 验证 准备 解析 初始化 销毁,在加载阶段,jvm会根据类的全限定名找到二进制字节流,并把这个二进制字节流加载进内存,转为运行时数据区的存储结构,最后创建一个java.lang.Class类型的实例作为方法区这个类型的访问入口,这里所说的Class实例就是类对象,这个类对象创建完成后会存放在堆区,这个动作是在加载阶段完成。
方法区中存放的是类的元数据,包括静态变量,有哪些属性,有哪些方法,继承的父类,实现的接口,异常相关的信息等等,而类对象就是这些信息的访问入口,Class类提供了很多api,这些api大多是native方法,也就说明这个类对象只是这个类在堆区的一个接口,由jvm底层来实现,jvm底层会根据每个api的功能去方法区拿类的信息。
2.反射的应用
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;
}
通过上面的这个例子,我们可以看到反射是如何应用的:
- 首先通过.class或者Class.forName()方法,获取一个Class实例,这个实例就是类对象,
- 然后通过调用这个Class实例的方法获取类的构造方法,得到构造方法Constructor实例
- 然后调用Constructor实例的newInstance方法进行实例化对象。
以上是利用反射机制创建对象,当然除了创建对象,还可以获取类的属性实例Field和方法实例Method,通过方法实例和属性实例的api对对象的方法和属性进行设置或者执行。这便是反射的应用。
3.反射的特点
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底层实现。掌握了这四个类,灵活运用,基本就掌握了反射机制。
1.Class类
- getClassLoader() 返回类加载器
- getClasses() 返回一个数组,该数组中包含该类中所有公共类和接口类的类对象
- getDeclaredClasses() 返回一个数组,数组中包含该类中所有类的和接口类的对象
- forName(String className) 根据类名返回类的类对象
- newInstance()创建类的实例
- getPackage()获取类的包
- getSimpleName() 获取类的名字
- getSuperclass() 获取当前类继承的类的名字
- getInterfaces() 获取当前类实现的类或者接口
- .class 获取当前对象的类对象
- getField(String str) 获取public的字段对象 只能得到public
- getFields() 获取所有public字段对象 只能得到public
- getDeclaredFild(String name) 取某个字段对象
- getDeclaredFilds() 取所有字段对象
- getAnnotation(Class) 获取注解
- getConstructor(String.calss...) 获取该类中对应参数类型的构造方法
- getConstructors()获取该类的所有公有构造方法
- getDeclaredConstructor(String.calss...)获取该类中与参数类型匹配的构造方法
- getDeclaredConstructors()获取所有构造方法
- getMethods()获取该类所有公有方法
- getMethod(String name,String.calss...) 获取该类对应名称和参数类型的公有方法
- getDeclaredMethods() 获取所有方法
- getDeclaredMethods(String name,String.calss...)获取该类对应名称和参数类型的方法
- isAnnotation()如果是注解类型返回true
- isnotationPresent(注解类型)如果是指定类型的注解返回true
- isArray()如果是数组类型返回true
- isEnum()如果是枚举类型 返回true
- isInstance(Object obj)如果传入的参数是该类的实例则返回true
- isInterface()如果是接口类型返回true
2.Field类
field是类中的成员变量:变量和属性是俩个概念,变量有get和set方法就是属性。
- get(Object obj)获取obj对象中对应的属性值
- set(Object obj,Object val)设置obj对象中对应属性值
- setAccessible() 暴力反射,忽略访问权限修饰符
3.Method类
- invoke(Object obj,object args...) 入对象及参数调用该对象的该方法
- getName() 取某个方法的名字
- setAccessible() 暴力反射,忽略访问权限修饰符
4.Constructor类
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,就可以突破私有权限,代码可以看上面破坏单例的例子。