Java核心技术读书笔记5-4 Java反射与代理
7.反射
能够分析类能力的程序称为反射,运用反射可以在运行时获取类与对象的信息,因此其主要使用人员为工具的构造者,在JavaBean中也大量运用了反射技术。下面是反射的一些应用:
·在运行时分析类的能力
·在运行时查看对象属性
·实现通用数组操作
·利用Method对象,实现函数式编程
7.1 Class类:在程序运行时系统为每个类维护的一个类,若一个类为可实例化的类,那么所有的类对象也会共享一个Class类。
//类的加载与类对象的获得
Employee e = new Employee();
Class<? extends Employee> classE = e.getClass(); //由对象获取其所属类的Class对象
Class<Employee> classE2 = Employee.class; //类直接返回其Class对象
System.out.println(classE == classE2); //true 说明类只维护一个Class对象
String classEName = classE.getName(); //包括包的完整类名,分隔符为'.',如 java.util.Random
Class<?> classE3 = Class.forName(classEName); //使用静态方法通过完整类名将类加载到JVM中并返回该类对象,参数只能是完整类名或接口名
实际上Class一个泛型类,也就是说通过Employee.class获得的是Class
//通过class对象实例化类对象
Employee employee = classE.newInstance(); //在1.9之后已经被弃用,会调用无参构造器。
Employee employee1 = classE.getDeclaredConstructor(double.class, String.class).newInstance(200, "王工"); //根据参数类型和参数调用构造器。
Employee employee2 = classE.getDeclaredConstructor().newInstance(); //若不指定参数则调用无参构造器。
7.2 利用反射能力检查类结构:反射包中的三个类Field,Method,Constructor分别用于描述类的域、方法和构造器。
//获取类的结构信息(元信息)
Class<?> mangerClass = Class.forName("smallPrac.Manager");
Class<?> employeeClass = mangerClass.getSuperclass(); //获取父类Class对象
int modifiers = mangerClass.getModifiers(); //返回索引:public, protected, private, final, static, abstract and interface等关键字组成的编码
System.out.println(Modifier.toString(modifiers)); //使用该方法解码,打印出public
boolean aFinal = Modifier.isFinal(modifiers); //false 检查是否含有某一个特定的修饰符
Constructor<?>[] constructors = employeeClass.getDeclaredConstructors(); //获取所有构造器组成的数组
System.out.println(Arrays.toString(constructors)); //[public smallPrac.Employee(), public smallPrac.Employee(double), public smallPrac.Employee(double,java.lang.String)]
for (Constructor c : constructors) {
c.getName(); //项目名称
Modifier.toString(c.getModifiers()); //修饰符
Class[] parameterTypes = c.getParameterTypes(); //参数类型数组
}
Field[] fields = employeeClass.getFields(); //取所有域组成的数组,包括从父类继承的
System.out.println(Arrays.toString(fields)); //[public static java.lang.String smallPrac.Employee.depart, public java.lang.String smallPrac.Employee.name]
for (Field f : fields) {
f.getName(); //项目名称
Modifier.toString(f.getModifiers()); //修饰符
f.getType(); //返回域类型
}
Method[] methods = employeeClass.getMethods(); //取所有方法组成的数组,包括从父类继承的
System.out.println(Arrays.toString(methods)); //[public java.lang.String smallPrac.Employee.getName(), public java.lang.String smallPrac.Employee.toString(),
for (Method m : methods) {
m.getName(); //项目名称
Modifier.toString(m.getModifiers()); //修饰符
m.getReturnType(); //返回类型
Class<?>[] parameterTypes = m.getParameterTypes(); //参数列表
}
methods[0].getDeclaringClass().getName(); //smallPrac.Employee 返回所属类
methods[0].getExceptionTypes(); //方法抛出的异常
constructors[0].getExceptionTypes(); //构造器抛出的异常
}
7.3 利用反射在运行时分析对象
//在运行时为对象注入属性值或获取值,主要作用是可以不经过方法访问私有域。
//获取
Employee employee = new Employee(1000d, "王工");
Class classE = employee.getClass();
Field nameField = classE.getDeclaredField("name"); //首先获取一个域对象
Object o = nameField.get(employee);//由域对象直接获取实例对象对应域的值。不用经过方法
System.out.println(o); // 输出王工
Field salaryField = classE.getDeclaredField("salary"); //那如果我们想获得的域是基本类型怎么办? 其实可以直接打印,也可以使用更清晰的getDouble方法
salaryField.setAccessible(true); //salary的访问控制是private的,必须设置这一项Accessible为true
double aDouble = salaryField.getDouble(employee);
System.out.println(aDouble); //1000.0
//设置
nameField.set(employee, "王工2");
System.out.println(employee.getName()); //王工2
7.4 构造数组
如何实现一个对任何类型参数(即Object)的数组拷贝方法?你必须知道传入数组的class类型,然后再由class类型创建新数组。所以就需要用到反射。
/**
* 根据入参数组和长度拷贝一个新的数组,长度大于原数组用默认值填充,长度小于原数组则进行截断
* @param a 一个任意类型的数组对象
* @param Length 结果长度
* @return
*/
public static Object copyOf(Object a, int Length){
Class<?> arrayClass = a.getClass();
if(!arrayClass.isArray()){ //若入参不是数组类型,返回null
return null;
}
Class<?> componentType = arrayClass.getComponentType(); //获取数组元素类型
int length = Array.getLength(a); //入参数组的实际类型
Object res = Array.newInstance(componentType, Length); //根据获得的元素类型建立一个同元素类型的数组,长度为提供的参数Length
System.arraycopy(a, 0, res, 0, Math.min(length, Length)); //拷贝
return res; //注意,返回参数必须是Object, someType[]可以转换成其父类Object但不能转换成Object[]
}
...
int[] array = new int[]{1, 2, 3, 4, 5};
int[] res = (int[])copyOf(array, 4);
System.out.println(Arrays.toString(res)); //[1, 2, 3, 4]
7.5 调用任意方法
与通过Field对象传入实例对象来设置、取属性类似,反射允许通过Method对象传入实例对象调用方法。
Employee employee = new Employee(1000d, "王工");
Class<? extends Employee> classE = employee.getClass();
Method methodGetName = classE.getMethod("getName"); ////获取Method对象 指定方法名
Method methodSetName = classE.getMethod("setName", String.class); //对于有参数的方法,指定方法名和参数
Method methodSetName2 = classE.getMethod("setName", String.class, String.class); //重载方法,指定不同参数
String res = (String)methodGetName.invoke(employee); //方法对象传入实例对象调用函数
System.out.println(res); //王工
methodSetName.invoke(employee, "王工2"); //调用设置方法
7.6 总结
反射不建议应用于应用程序,编译器很难帮助发现程序中的错误。建议仅在必要的时候在使用Method对象,最好用接口和lambda表达式完成相同的功能。
浙公网安备 33010602011771号