0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java反射机制 ;最后还顺带提出了 继承设计的技巧;
1)反射定义:能够分析类能力的程序称为反射;
2)反射机制可以用来:
2.1) Java 运行时系统始终为 所有对象维护一个称为运行时的类型标志;虚拟机 利用运行时类型信息选择相应的方法执行;(干货——Java 运行时系统始终为 所有对象维护一个称为运行时的类型标志,对象在虚拟机中的运行时类信息保存在Class对象中)
2.2)Class: 可以通过专门的java类访问这些信息, 保存这些信息的类被称为 Class;Object.getClass() 将 返回一个Class 类型的实例;
2.3) 如同用一个Employee 对象表示一个特定的雇员属性一样, 一个 Class 对象将表示一个特定类的属性;最常用的 Class 方法是 getName方法(类名);(Class类的定义 的良好诠释)
2.4)还可以调用静态方法forName 获得类名对于的Class对象;
String className = "java.util.Date";
Class c1 = Class.forName(className);
Hint)在启动时,main方法需要加载所有的类, 对于一个大型的应用程序来说, 这将会消耗很多时间, 用户会因此感到不耐烦;我们可以通过 Class.forName 手动地加载其他的类;
2.5)获得Class 类对象的第3种方法:
1)第一种方法:
Date d = new Date();
Class c1 = d.getClass();
String name = c1.getName();
2)第二种方法:
String className = "java.util.Date";
Class c1 = Class.forName(className);
3)第三种方法:
如果T是 java类型, T.class 将代表匹配的类对象, 如
Class c1 = Date.class;
Class c2 = int.class;
Class c3 = Double[].class;
Attention)一个 Class 对象实际上表示的是一个类型, 而这个类型未必一定是一种类;例如 int 不是类, 但 int.class 是一个Class类型的对象;(干货——一个 Class 对象实际上表示的是一个类型, 而这个类型未必一定是一种类,如int不是类型,而int.class是一个Class类型的对象,那我们就可以人为 obj.class 是obj在jvm中运行时相关信息的封装对象)
Annotation)从 Java SE 5.0 开始, Class类已经参数化了,如, Class 的类型是 Employee.class;
Warning)由于历史原因, getName 应用于数组类型的时候会返回一个 很奇怪的名字:
Double[].class.getName() 返回 ”[Ljava.lang.Double“;
int[].class.getName() 返回 ”[I“;
2.6)虚拟机为每个类型管理一个 Class对象: 因此,可以用 == 运算符实现两个类对象的比较操作;
2.7)newInstance()方法: 可以用来快速创建一个类的实例;.getClass().getInstance(); 它将创建一个与 e 具有相同类型的实例, newInstance 调用默认构造器, 初始化新创建的对象,如果这个类没有默认的构造器,就会抛出一个异常;
Annotation)
3.1)当程序运行过程中发生错误时, 就会抛出异常。抛出异常比终止程序要灵活得多, 这是因为可以提供一个捕获异常的处理器(handler)对异常情况进行处理;
3.2)异常有两种类型(注意区别):未检查异常和已检查异常;
3.3)最简单的异常处理器
try catch块;
3.4)如果类名不存在, 则将跳过try 块中的剩余代码, 程序直接进入catch 子句, 这里,利用Throwable 类的 printStackTrace 方法打印出栈的轨迹;
4.1)反射机制最重要的内容——检查类的结构;
4.2)在java.lang.reflect 包中有3个类 Field、Method 和 Constructor 分别用于描述类的域、方法 和 构造器;
4.3)这3个类都有一个 getName的方法, 用来返回项目名称
4.4) Class类中的 getField、getMethod 和 getConstructor 方法将分别返回类提供的public 域、方法和构造器数值,其中包括超类的公有成员;
4.5) Class 类的 getDeclareFields、getDeclareMethods、getDeclaredConstructors 方法将分别返回类中声明的全部域、方法和构造器;其中包括私有方法和受保护成员,但不包括超类成员;
5.1)本节将进一步查看数据域的实际内容;
5.2)利用反射机制可以查看在编译时还不清楚的对象域;
5.3)查看对象域的关键方法是 Field类中的 get方法;如果 f 是一个 Field 类型的 对象, obj 是某个包含 f 域的类对象, f.get(obj) 将返回一个对象,其值为obj 域的当前值;
5.4)看个荔枝:
对以上代码的分析(Analysis):
5.5)引入setAccessible 方法(打印结果与5.4做比较,这个很有必要): 反射机制的默认行为受限于 Java的访问控制, 然而,如果一个 java 程序没有收到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用 Field、Method、Constructor对象 的 setAccessible 方法;如,f.setAccessible(true);
5.6)get方法 所遇到的问题 + 解决方法
5.6.3)当然, 可以获得就可以设置; 调用 f.set(obj, value) 可以将 obj 对象的 f域设置成新值;
5.6.4)如何编写一个可供任意类使用的通用 toString()方法;(P203,非常重要)
6.1)java.lang.reflect 包中的Array 类允许动态地创建数组。例如, Array类中的copyOf 方法,用于扩充数组容量:
int[] a = new int[100];
a = Arrays.copyOf(a, 2*a.length());
6.2)如何编写这样一个通用的方法?
public static Object[] badCopyOf(Object[] a, int newLength)
{
Object[] newArray = new Object[newLength];
System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
return newArray;
}
Attention)
Object newArray = Array.newInstance(componentType, newLength);
6.3)为了能够实际运行, 需要获得新数组的长度和元素类型: 可以通过 调用 Array.getLength(a) 获得数组的长度, 也可以通过 Array类的静态方法 getLength 方法的返回值得到任意数组 的长度。 而要获得新数组元素类型, 就需要进行以下工作(Work):
7.1)在Method类中有一个invoke 方法, 它允许调用包装在当前 Method 对象中的方法, invoke方法的签名是: Object invoke(Object obj, Object… args), 如 String n = (String)m1.invoke(harry);
7.2)如何得到 Method 对象呢?
7.2.1)通过 getDeclaredMethods 方法 , 然后对返回的Method对象数组进行查找, 直到发现想要的方法为止;
7.2.2)通过调用 Class 类中的getMethod 方法得到想要的方法;它与 getField 方法类似, getField 方法根据表示域名的字符串, 返回一个 Field对象;然而,有可能存在着若干个相同名字的方法, 因此要格外小心, 以确保准确地得到想要的方法;所以, 我们需要提供想要方法的参数类型;
7.3)getMethod的方法签名是: Method getMethod(String name, Class… parameterTypes) ;
7.4)看个荔枝(如何获得 Employee类的 getName 方法 和 raiseSalary 方法的方法指针):
Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("getSalary", double.class);
7.5)再看个荔枝
这里, f是一个Method类型的对象,由于正在调用的 方法是一个静态方法,所以 invokle 的第一个参数是null;
为了将 Math.sqrt 函数表格化, 需要将f 设置为: Math.class.getMethod(“sqrt”, double.class);这是 Math类中的一个方法, 通过参数向它提供了一个函数名 sqrt 和 一个double类型参数;
对以上代码的分析(Analysis):
Attention)
8.1)将公共操作和域放在超类;
8.2)不要使用受保护的域:
8.3)使用继承实现 “is-a” 关系;
8.4)除非所有继承的方法都有意义, 否则不要使用继承;
8.5)在覆盖方法时, 不要改变预期的行为;
8.6)使用多态,而非类型信息:无论什么时候 , 碰到这种代码,都应该使用 多态性;
if(x is of type1)
action1(x);
else if(x is of type2 )
action2(x);
8.7)不要过多地使用反射: