一、反射
反射机制是 java 语言特性之一,是 java 被视为动态语言的(或准动态)一个关键特性。
在计算机领域,能够自描述和自控制,即在运行状态中,动态获取类信息及动态调用实例方法的能力。
java 反射有以下3个动态特性
- 运行时创建实例
- 运行期间调用方法
- 运行时更改属性
反射机制允许 Java 程序加载一个允许时才得知其名的类,获悉其完整 API 信息,包括其修饰符、超类、实现的接口,也包括属性和方法的所有信息;并可生成其实例,对其属性赋值或调用其方法,通过 Java 反射可以实现以下功能。
- 在运行时探知任意一个实例所属的类
- 在运行时构造任意一个类的实例
- 在运行时探知任意一个类所属具有的方法和属性
- 在运行时调用任意一个实例的方法
使用 Java 反射技术,常用的类如下:
java.lang.Class<T> 类: 反射的核心类,反射所有的操作都是围绕该类来生成的,通过Class类可以获取类的属性,方法等内容信息。
java.lang.reflect.Constructor<T> 类: 表示类的构造方法。
java.lang.reflect.Field 类: 表示类的属性,可以获取和设置类中属性的值。
java.lang.reflect.Method 类: 表示类的方法,可以用来获取类中方法的信息或执行方法。
使用反射的基本步骤如下
- 导入 java.lang.reflect 包中的相关类
- 获取需要操作的类的 Class 实例
- 调用 Class 实例的方法获取 Field,Method 等实例
- 使用反射 API 操作实例成员
获取 Class 实例通常有如下3中方式
-
调用类或接口实例的 getClass() 方法
-
Class clz = obj.getClass(); //obj 为某个类型的实例
-
-
调用类或接口的 class 属性
-
Class cla = Student.class; // Student 为自定义的学生类型
-
-
使用 Class.forName()方法
-
Class clz = Class.forName("com.mysql.cj.jdbc.Driver");
-
从 Class 实例获取信息,
获取对应类型的基本信息
String getName() 以字符串形式返回该类型的名称
String getSimpleName() 以字符串形式返回该类型的简称
Package getPackage() 获取该类型所在的包
Class getSuperclass() 返回该类型的超类 Class 实例
Class[] getInterfaces() 返回该类型所实现的全部接口的 Class 实例
int getModifiers() 返回该类型的所有修饰符,有 public、protected、private、final、static、abstract 等对应的 int常量组成,返回的整数应使用 Modifier 工具类来解析,才可以判断修饰符的构成
Class[] getDeclaredClasses() 返回该类型中包含的全部内部类的 Class 实例
Class getDeclaringClass() 返回该类型所在的外部类的 Class 实例
获取对应类型所包含的构造方法,常用方法
Constructor getConstructor(Class…… params) 返回该类型指定参数列表的 public 构造方法,构造方法的参数列表与 params所指 定的类型列表所匹配
Constructor getConstructors() 返回该类型的所有 public 构造方法
Constructor getDeclaredConstructor(Class…… params) 返回该类型的指定参数列表的构造方法,访问级别不限
Constructor getDeclaredConstructors() 返回该类型的所有构造方法,访问级别不限
获取对应类型所包含的属性,不包括继承的属性
Field getField(String name) 返回该类型中指定的名称的 public 属性,name 参数用于指定属性名称。
Field[] getFields() 返回该类型的所有 public 属性
Field getDeclaredField(String name) 返回该类型指定名称的属性,与属性的访问级别无关
Field[] getDeclaredFields() 返回该类型的全部属性,与属性的访问级别无关
访问对应类型所包含的方法
Method getMethod(String name, Class…… params) 返回该实例中指定的 public 方法,name 参数用于指定方法名称,params参数 指定参数列表
Method[] getMethods() 返回该实例中所有 public 方法
Method getDeclaredMethod(String name, Class…… params) 返回该实例中指定的方法,与方法的访问级别无关
Method[] getDeclaredMethods() 返回该实例中的全部方法,与方法的访问级别无关
创建实例
- 使用 Class 实例的 newInstance() 方法创建相关类型的实例
- 使用 Constructor 实例创建相关类型的实例
- 获取与该类型相关的 Class 实例。
- 调用 Class 实例的方法获取表示指定构造方法的 Constructor 实例
- 调用 Constructor 实例的 newInstance() 方法来创建相关类型的实例
注意
受访问修饰符的限制,使用反射方式访问超出访问范围的构造方法、属性、方法时,会引发 java.lang.IllegalAccessException 异常。
若要静止 Java 语言访问检查强行访问,需要设置相关实例为可访问,语法如下:
c3.setAccessible(true); // 通过构造方法/属性/方法实例调用
当前此种做法会破坏封装,需谨慎使用。
使用 Field 实例可以对属性进行取值或赋值操作
xxx getXxx(Object obj) xxx表示8种基本数据类型之一,如 int getInt(Object obj).obj 为该属性所在类的实例。
Object get(Object obj) 以 Object 类型返回 obj 中相关属性的值
void setXxx(Object obj, xxx val) 将 obj 中相关属性的值设置为 val。xxx 为8种基本数据类型之一
void set(Object obj, Object val) 将 obj 中相关属性的值设置为 val
void setAccessible(boolean flag) 对相关属性设置访问权限。设置为 true 可以静止 Java 语言访问检查
根据参数执行方法返回正确的参数类型
private static Object parseValue(String value, Class type) throws ParseException{
if(String.class == type)
return value;
if(java.util.Date.class == type)
return new SimpleDateFormat("yyyy-MM-dd").parse(value);
if(boolean.class == type || Boolean.class == type)
return true;
if(char.class == type || Character.class == type)
return value.charAt(0);
if(int.class == type || Integer.class == type)
return Integer.valueOf(value);
if(short.class == type || byte.class == type || long.class == type || float.class == type || double.class == type)
try { // 将值类型变更为对应的包装类型
// type = Class.forName("java.lang." + type.getName().substring(0, 1).toUpperCase() + type.getName().substring(1));
type = Class.forName("java.lang." + type.getSimpleName().substring(0, 1).toUpperCase() + type.getSimpleName().substring(1)); // ???
}catch (ClassNotFoundException e){
e.printStackTrace();
}
if(type.getName().startsWith("java.lang.") || type.getName().startsWith("java.math."))
try {
//BigDecimal 和 BigInteger 没有 valueOf() 方法
// return type.getMethod("valueOf", String.class).invoke(null, value[0]);
return type.getConstructor(String.class).newInstance(value);
}catch (Exception e){
}
return null;
}
知识扩展
在 java.lang.reflect 包下还提供了一个 Array 类,Array 实例可以代表一个数组。程序可以使用Array类来动态地创建数组及操作数组元素等。
static Object newInstance(Class componentType, int length) 创建元素类型为 componentType,长度为 length 的数组
static Object newInstance(Class componentType, int…… dimensions) 创建多维数组,元素类型时 componentType,维数是 dimensions.length,dimensions 中的每一个元素决定每一维的长度
static int getLength(Object arr) 返回数组 arr 的长度
static xxx getXxx(Object arr, int index) xxx表示8种基本数据类型之一,以基本数据类型返回数组arr中索引为index的元素
static Object get(Object arr, int index) 以Object类型返回数组arr中索引为index的元素
static void setXxx(Object arr, int index, xxx val) 将val赋值给数组arr中索引为index的元素,xxx为8种基本数据类型之一
static void set(Object arr, int index, Object val) 将val赋值给数组arr中索引为index的元素
注意
使用反射虽然会很大程度上提高代码的灵活性,但是不能滥用反射,因为通过反射创建和访问实例时性能要稍微低一些,且反射可能会破坏封装,实际上,只有当程序需要动态创建类的实例时才会考虑使用反射。