一、反射
在Java运行环境中,对于任意一个类,都能够知道它的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取类信息以及动态调用对象方法的功能被称为Java语言的反射(Reflection)机制。反射机制可以赋予Jvm动态编译的能力,这是Java被视为动态(或准动态)语言的一个关键性质。Java中有两种编译类型:
- 静态编译:在编译时确定类型,绑定对象
- 动态编译:在运行时确定类型,绑定对象
所谓编译期就是编译器帮你把源代码翻译成机器能识别的代码,比如编译器把Java代码编译成Jvm能识别的字节码文件;而运行期指的是将可执行文件交给操作系统去执行。一般情况下,在Java中声明一个变量时,必须知道它的类型,变量的类型信息在编译时就保存到了class文件中,程序在运行时是固定不变的;而反射机制允许静态语言在运行时检查、修改程序的结构与行为。动态编译最大限度地发挥了Java的灵活性,体现了多态的应用,降低了类之间的耦合。
二、常用方法
如果说类是对具有相同特征实体的抽象,那Class类就是对类的抽象,毕竟所有类都由属性、方法和构造函数等组成,也可以说Class是类的类。抛开类的具体功能,单从类的结构看,一个类可以拆分成属性、方法和构造函数。利用反射可以将类解剖,并将各个组成部分映射成一个个Java对象,通过这些对象最终达到调用类方法和属性的目的。
与反射机制相关的类有:
- Class——代表类的实体,在运行的Java应用程序中表现为类和接口
- Field——代表类的成员变量
- Method——代表类的方法
- Constructor——代表类的构造方法
各个类的常用方法汇总如下:
类 | 方法族 | 方法 | 用途 |
---|---|---|---|
Class | 获取类相关的方法 | asSubclass(Class<U> clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 | ||
getClassLoader() | 获得类的加载器 | ||
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 | ||
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 | ||
forName(String className) | 根据类名返回类的对象 | ||
getName() | 获得类的完整路径名字 | ||
newInstance() | 创建类的实例 | ||
getPackage() | 获得类的包 | ||
getSimpleName() | 获得类的名字 | ||
getSuperclass() | 获得当前类继承的父类的名字 | ||
getInterfaces() | 获得当前类实现的类或是接口 | ||
获得类中属性相关的方法 | getField(String name) | 获得某个公有的属性对象 | |
getFields() | 获得所有公有的属性对象 | ||
getDeclaredField(String name) | 获得某个属性对象 | ||
getDeclaredFields() | 获得所有属性对象 | ||
获得类中注解相关的方法 | getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 | |
getAnnotations() | 返回该类所有的公有注解对象 | ||
getDeclaredAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的所有注解对象 | ||
getDeclaredAnnotations() | 返回该类所有的注解对象 | ||
获取类中构造器相关的方法 | getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 | |
getConstructors() | 获得该类的所有公有构造方法 | ||
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 | ||
getDeclaredConstructors() | 获得该类所有构造方法 | ||
获取类中方法相关的方法 | getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 | |
getMethods() | 获得该类所有公有的方法 | ||
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 | ||
getDeclaredMethods() | 获得该类所有方法 | ||
获取类状态相关的方法 | isAnnotation() | 如果是注解类型则返回true | |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true | ||
isAnonymousClass() | 如果是匿名类则返回true | ||
isArray() | 如果是一个数组类则返回true | ||
isEnum() | 如果是枚举类则返回true | ||
isInstance(Object obj) | 如果obj是该类的实例则返回true | ||
isInterface() | 如果是接口类则返回true | ||
isLocalClass() | 如果是局部类则返回true | ||
isMemberClass() | 如果是内部类则返回true | ||
Field | equals(Object obj) | 属性与obj相等则返回true | |
get(Object obj) | 获得obj中对应的属性值 | ||
set(Object obj, Object value) | 设置obj中对应属性值 | ||
Method | invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 | |
Constructor | newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
三、实战应用
先看一个Android源码中运用反射的例子:
boolean mbHasLoadSettingClass = false; Method mmgetIntForUser = null; String mStrFPS_ONENAV_ENABLED=""; Class<?> mclzModeSettingsSecure = null; boolean hasNavBar(Context context) { try { if (!mbHasLoadSettingClass) { mclzModeSettingsSecure = Class.forName("com.motorola.android.provider.MotorolaSettings$Secure"); //获取Secure类的Class实例 Field fFPS_ONENAV_ENABLED = mclzModeSettingsSecure.getField("FPS_ONENAV_ENABLED"); //获取FPS_ONENAV_ENABLED属性对象 String strFPS_ONENAV_ENABLED = ""; if (fFPS_ONENAV_ENABLED != null) { mStrFPS_ONENAV_ENABLED = (String) fFPS_ONENAV_ENABLED.get(null); //读取fFPS_ONENAV_ENABLED属性值 } mmgetIntForUser = mclzModeSettingsSecure.getDeclaredMethod("getIntForUser", ContentResolver.class, String.class, int.class, int.class); //获取getIntForUser方法对象 } if (mmgetIntForUser != null && mclzModeSettingsSecure != null) { int iRet = (int) mmgetIntForUser.invoke(mclzModeSettingsSecure, context.getContentResolver(), mStrFPS_ONENAV_ENABLED, -1, ActivityManager.getCurrentUser()); //调用getIntForUser方法 if (iRet != -1) { return false; } } } catch (ClassNotFoundException e) { } catch (NoSuchMethodException e) { } catch (NoSuchFieldException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } }
反射的用法相对固定,主要流程如下:
在反射调用过程中,需要注意:
- 静态方法和变量在加载类时已经分配内存,通过Class实例就可以直接访问;调用时可以直接传入null或Class
- 非静态方法和变量取决于类的实例,必须创建类实例后才能访问;
- 在反射中无法直接调用private修饰的方法或变量,需要先通过如下方法取消访问限制
- 引用静态常量的地方在编译时已经替换为常量,所以修改静态变量并不会改变已经编译的对象逻辑
method.invoke(null); //调用静态方法 field.set(null,false); //设置静态变量 method.invoke(clazz.newInstance()); //调用非静态方法 field.set(clazz.newInstance(),false); //设置非静态变量 method.setAccessible(true); //取消访问限制
日常开发中反射并不常用,主要有如下四个场景会用到反射:
- 注解,注解底层实现就是反射
- 编写框架,反射机制是很多Java框架的基石,如xml或properties配置文件
- 其他编译期无法确定类名,需要在运行期从配置文件动态读取的情况,比如加载数据库驱动程序
- 访问系统内部方法,如果方法内部有权限检查,反射无效
总结:反射机制最大限度地发挥了Java的灵活性,使Java具有动态语言的特性。但是除非可以大大简化业务代码,反射并不被鼓励使用,原因之一是造成代码可读性降低;原因之二是反射需要在运行期重新解析,降低运行效率。