1 反射
1.1 什么是反射
正射:指的是我们知道类的定义和类中的方法名称,直接先创建对象,然后通过对象去调用方法。例如:
Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);
反射:指的是一开始不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象,需要用JDK 提供的反射 API 进行反射调用。需要通过类的路径字符串创建对象,通过方法名称字符串调用方法。例如:
Class clz = Class.forName("com.chenshuyi.reflect.Apple");//通过字符串获取类Class
Constructor constructor = clz.getConstructor();//获取类的构造器
Object object = constructor.newInstance();//创建对象
Method method = clz.getMethod("setPrice", int.class);//通过方法名获取方法
method.invoke(object, 4);//调用方法;
第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
1.2 反射获取类、构造器、方法、属性
(1)获取类
使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
(2)获取构造器
通过 Constructor 对象的 newInstance() 方法
Constructor constructor = clz.getConstructor();//获取类的构造器
Object object = constructor.newInstance();//创建对象
(1) 获取方法
获取方法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 14);
(2) 获取属性
过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
Price
(5)获取包含私有属性
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:
Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
name
price
1.3 反射的源码解析
(1)Method的invoke方法
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
(2)acquireMethodAccessor 返回MethodAccessor对象
private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp;
}
(3)反射工厂返回对象reflectionFactory.newMethodAccessor
public MethodAccessor newMethodAccessor(Method var1) {
checkInitted();
if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
} else {
NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
var2.setParent(var3);
return var3;// 最终返回的是DelegatingMethodAccessorImpl对象
}
}
其中DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
是将NativeMethodAccessorImpl var2对象作为入参,传入DelegatingMethodAccessorImpl的构造函数。那么构造函数内部做了什么?很关键!
DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
this.setDelegate(var1);对象设置在Delegate属性中,后面会用到
}
delegate 属性的定义是private MethodAccessorImpl delegate;
(4)步骤(3)最终返回的是DelegatingMethodAccessorImpl对象,所以步骤(1)中return ma.invoke(obj, args);函数invoke是DelegatingMethodAccessorImpl的方法。看看DelegatingMethodAccessorImpl的invoke方法定义如下:
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
return this.delegate.invoke(var1, var2);//调用了属性delegate的invoke方法;也就是NativeMethodAccessorImpl对象的invoke方法; }
(5)NativeMethodAccessorImpl对象的invoke实现
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {//累计大于阈值,切换
if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
this.parent.setDelegate(var3);
}
return invoke0(this.method, var1, var2);
}
每次NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器numInvocations,看超过阈值 没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的 MethodAccessor的实现类,并且通过this.parent.setDelegate(var3);改变DelegatingMethodAccessorImpl所引用的MethodAccessor为 Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。
注意到关键的invoke0()方法是个native方法。它在HotSpot VM里是由JVM_InvokeMethod()函数所支持的,不能在深入了:
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
return JVM_InvokeMethod(env, m, obj, args);
}
为什么要这样先用native后又切换为java的呢?
就像注释里说的,实际的 MethodAccessor 实现有两个版本,一个是 Native 版本NativeMethodAccessorImpl,一个是 Java 版本MethodAccessorImpl。Java版本在第一次启动时,需要加载字节码实现Method.invoke() 和 Constructor.newInstance() ,比较耗时。所以Native 版本第一次启动比java版本快3-4倍,但是后面的调用java版本比Native快20倍。所以为了避免启动慢,第一次使用native版本快速启动。为了避免后续运行慢,在切换到java版本MethodAccessorImpl 对象去实现反射。
自己编了一个股票监控软件,有如下功能,有兴趣的朋友可以下载;
(1) 个股监测。监测个股实时变化,可以监测个股大单交易、急速拉升和下降、主力入场和出场、股票最高点和最低点提醒。检测到最高点、最低点、主力进场点、主力退场点、急速拉升点、急速下跌点,给出语音或者声音提醒,不用再时刻看着大盘了,给你更多自由的时间;
(2) 大盘监测。监测大盘的走势,采用上证、深证、创业三大指数的综合指数作为大盘走势。并实时监测大盘的最高点和最低点、中间的转折点。
(3) 股票推荐。还能根据历史数据长期或短期走势进行分析,对股市3千多个股票进行分析对比,选出涨势良好的股票,按照增长速度从大到小排序,推荐给你涨势良好的股票;
下载地址:
1.0.3版本(修复大盘指数崩溃缺陷)下载地址:
链接:https://pan.baidu.com/s/1BJcTp-kdniM7VE9K5Kd3vg 提取码:003h
更新链接:
https://www.cnblogs.com/bclshuai/p/10621613.html