反射
一、什么是反射
反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活应用的主要工具。
二、作用
-
调用一些私有方法,实现黑科技。比如双卡短信发送、设置状态栏颜色、自动挂电话等。
-
实现序列化与反序列化,比如PO的ORM,Json解析等。
-
实现跨平台兼容,比如JDK中的SocketImpl的实现
-
通过xml或注解,实现依赖注入(DI),注解处理,动态代理,单元测试等功能。比如Retrofit、Spring或者Dagger
三、基本使用
代码块
package com.example.myapplication.reflect;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class Apple {
private String appleName;
private int price;
private static final String name = new String("巫婆的苹果");
private Apple() {}
private Apple (String appleName, int price) {
this.appleName = appleName;
this.price = price;
}
private void setPrice(int price) {
this.price = price;
}
private int getPrice () {
return price;
}
private String getName() {
return name;
}
public static void main(String[] args) {
Apple apple = new Apple();
try {
//三种获取Class的方法,反射主要用第一种
Class clz = Class.forName("com.example.myapplication.reflect.Apple");
Class clz1 = apple.getClass();
Class clz2 = Apple.class;
//获取类实例的两种方法
Apple apple1 = (Apple) clz.newInstance();
Apple apple2 = (Apple)clz1.getDeclaredConstructor(String.class, int.class).newInstance("白雪公主", 1000);
System.out.println("apple name:"+apple2.appleName);
//获取方法对象
Method method = clz.getDeclaredMethod("setPrice", int.class);
method.setAccessible(true);
method.invoke(apple1, 5);
Method getMethod = clz.getDeclaredMethod("getPrice");
getMethod.setAccessible(true);
System.out.println("反射方法对象获取price"+getMethod.invoke(apple1));
//遍历方法
Method[] methods = clz.getDeclaredMethods();
AccessibleObject.setAccessible(methods,true);
for(Method method1 :methods) {
// method1.setAccessible(true);
System.out.println("类中的方法有:"+method1.getName());
}
//获取变量
Field field = clz.getDeclaredField("price");
field.setAccessible(true);
int newPrice = field.getInt(apple1);
System.out.println("直接反射变量获取到的price"+newPrice);
//遍历变量
for(Field field1 : clz.getDeclaredFields()) {
field1.setAccessible(true);
System.out.println("获取到的变量名:"+field1.getName());
}
//获取final变量 ,被内联的没用,这样似乎也没什么用,上面的new String("巫婆的苹果") 是为了防止内联
Field nameField = apple1.getClass().getDeclaredField("name");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);
nameField.setAccessible(true); //这个同样不能少,除非上面把 private 也拿掉了,可能还得 public
nameField.set(null, "巫婆的第二个苹果");
System.out.println("修改final变量后 "+apple1.getName());
} catch (Exception e) {
System.out.println("exception:"+e.getMessage());
}
}
}
输出:
apple name:白雪公主
反射方法对象获取price5
类中的方法有:main
类中的方法有:getName
类中的方法有:getPrice
类中的方法有:setPrice
直接反射变量获取到的price5
获取到的变量名:appleName
获取到的变量名:price
获取到的变量名:name
修改final变量后 巫婆的第二个苹果
四、怎么理解反射慢
在Stackoverflow上认为反射比较慢的程序员主要有如下看法
-
验证等防御代码过于繁琐,这一步本来在link阶段,现在却在计算时进行验证
-
产生很多临时对象,造成GC与计算时间消耗
-
由于缺少上下文,丢失了很多运行时的优化,比如JIT(它可以看作JVM的重要评测标准之一)
当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。
优化反射:
为什么反射慢:
1)getMethod和getDeclaredField方法会比invoke和set方法耗时;
2)随着测试数量级越大,性能差异的比例越趋于稳定;
如何优化:
1)善用API
比如,尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法。
2)缓存大法好
比如,需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多。还有将反射得到的method/field/constructor对象做缓存。
3) 尽量使用高版本jdk
4)ReflectASM通过字节码生成的方式加快反射速度
http://www.importnew.com/21211.html
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html