能够分析类能力的程序成为反射,反射机制基本用途如下:
- 在运行时分析类的能力。
- 在运行中查看对象。
- 实现通用的数组操作代码。
- 利用Method对象。
反射是一种功能强大且复杂的机制,使用它主要用来构造工具而不是进行应用程序的开发。
得到Class对象的三种方法:
1.利用对象: Class c = new Object().getClass();
2.利用Class的静态方法: Class c = Class.forName("类名带完整包名");
3.利用T.class:假设T是任意java类型,则有Class c = T.class
利用反射分析类的能力
反射机制最重要的内容——检查类的结构
在java.lang.reflect包中有三个类Field、Method和Construct分别用于描述类的域、方法、构造器。这三个类都有一个叫做getName的方法,分别返回对应项的名称。
常用方法:
java.lang.reflect.*;
Field[] getFields()
//返回一个包含Field对象的数组,这些对象记录了这个类和其父类所有的公有域
Field[] getDeclaredFields()
//返回同上,只是不是公有域而是所有域,如果类中没有域,或者Class对象描述的是基本类型或者数组类型,那么将返回一个长度为0的数组
Method[] getMethods()
//返回包含此类所有公有方法的Method对象的数组,包括从父类继承来的公有方法
Method[] getDeclaredMethods()
//返回包含Method对象的数组,区别于getMethods(),此方法将返回这个类或者接口的全部方法,但不包括从父类继承来的方法。
Constructor[] getConstructors()
//返回包含Constructor对象的数组,其中包含了Class对象所描述的类的所有公有构造器
Constructor[] getDeclaredConstructors()
//返回包含Constructor对象的数组,其中包含了Class对象所描述的类的所有构造器(不仅仅是公有)
Class getDeclaringClass()
//如果此 Class 对象所表示的类或接口是另一个类的成员(内部类),则返回的 Class 对象表示该对象的声明类。如果该类或接口不是其他类的成员,则此方法返回 null。如果此 Class 对象表示一个数组类、基本类型或 void,则此方法返回 null。
Class getEnclosingClass()
//返回基础类的立即封闭类。如果基础类是顶层类,则此方法返回 null(基本用法同getDeclaringClass(),唯一不同的是getEnclosingClass()在匿名内部类的Class对象调用下返回声明类,而getDeclaringClass()则返回null值)
Class[] getExceptionTypes()
//(在Constructor和Method类中)返回一个用于描述方法抛出的异常类型的Class对象数组
int getModifiers()
//可以利用Modifier的toString(int mod)方法得到这个整型数值的含义。
String getName()
//返回一个用于描述类、构造器、方法、或域名字符串
Class[] getParamterTypes()
//返回方法或构造器的参数对象的Class数组
Class getReturnType()
//返回一个用于描述返回类型的Class对象
Object newInstance(Object[] args)
//构造一个这个构造器所属类的新实例,args代表提供给构造器的参数
在运行时利用反射分析对象
如果f是一个Field类型的对象,obj是某个包含f域的对象,f.get(obj)将返回一个对象,其值是obj域的当前值,如下:
Employee harry = new Employyee("maiher",150000,1,1986);
Class c1 = harry.getClass();
Field f = c1.getField("name");//这里假设name是一个公有域
Object v = f.get(harry);
String str = (Object)v;//str的值为maiher
需要注意的一点是name是公有域,如果是私有域的话则会抛出一个IllegalAccessException异常。除非拥有访问权限,否则java安全机制只允许查看任意对象有哪些域,而不允许读取它们的值。
默认的情况下反射机制受限于java的访问控制,然而,如果一个java程序没有收到安全管理器的控制,就可以覆盖访问控制,为了达到这个目的,需要调用Field或者Constructor或者Method类的setAccessible()方法。
例如:f.setAccessible(true);
这时候就算name是私有域,也可以利用反射机制来访问,这里还有一个问题就是基本类型和对象类型的问题,如上所属,v是一个String类型的对象,那么可以转化成String,如果是double怎么办?一个是转化成Double,反射机制会自动打包成Double,另一个方法是利用Filed的getDouble方法。
可以读取就可以修改,可以调用f.set(obj,value)来讲obj对象的f域设置成新值。
常用方法:
void setAccessible(boolean flag)
//为反射对象设置可访问标志,如果为false,则受限于该类访问权限,如果为true,则屏蔽java语言的访问检查,即私有域也可以被查询和设置
boolean isAccessible()
//返回反射对象的可访问标志的值
static void setAccessible(AccessibleObject[] array,boolean flag)
//是一种设置对象数组可访问标志的快捷方法
Field getField(String name)
//得到指定域的Field对象
Field[] getField()
//返回包含所有域的数组
Object get(Object obj)
//返回obj对象中用Field表示的域值
void set(Ojbect obj,Object value)
//将value设置成新值
利用反射实现泛型数组代码
java.lang.reflect.Array类允许动态的创建数组,例如,把这个特性应用到Array中的copyOf方法实现中,应当记得这个方法可以用于扩展已经填满的数组。
Employee[] a = new Employee[100];
…
//a is full
a = Arrays.copyOf(a,2*a.length);
如果编写一个这样的通用方法呢?
为了编写这类通用的数组代码,需要能够创建于原数组类型相同的新数组。为此,需要使用Array类中的一些方法:
Object newArray = Array.newInstance(componentType,newLength);
componentType代表的是原数组类型,newLength代表的是数组长度
为了得到原数组类型,需要下面三步:
1.获取原数组类型的类对象
2.确认它是一个数组
3.使用Class的getComponentType方法确定数组对应的类型
代码如下:
public static Object copyOf(Ojbect a,int newLength){
Class c1 = a.getClass();
if(!c1.isArray()) return null;
Class cpt = c1.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(cpt,newLength);
System.arraycopy(a,0,newArray,0,Math.min(length,newLength));
return newArray;
}
注意:
1.一个对象数组可以转成Object对象数组然后转回来(java数组会记得new的时候的类型),但是一开始new的时候就是Object的对象数组不能转成需要的对象数组,如果这样会产生ClassCaseException异常。
2.为了实现上述操作,a的类型是Object而不是Object[] ,int[]可以被转成Object却不能转成对象数组(数组也是对象,万物皆对象)。
利用Method对象
可以利用Method对象来调用任意方法,在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法,invoke方法的签名是:
Object invoke(Object obj,Object...args)
对于静态方法,可以将第一个参数设为null
Method m1 = Employee.class.getMethod("getName");
String str = (String)m1.invoke(harry);
Double n = (Double)m1.invoke(harry);
//harry是实例化的Employee对象
注意:
1.invoke的参数和返回值必须是Object类型的。这就是说需要进行多次的类型转换,这样做将会是编译器错过检查代码的机会,因此,等到测试阶段才会发现这些错误,找到并改正则更加困难,不仅如此,使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。所以不建议在没必要使用的时候使用Method对象,使用接口进行回调,会是代码执行的速度更快,更易于维护。
总结:
a) 反射机制可以使人们可以在运行时查看方法和域,让人编写出更具通用性的程序。这种功能对编写系统程序来说极为有用,但通常不适用于编写应用程序。
b) 反射会增加程序的复杂性,编译器很难帮助人发现程序中的错误,任何错误只能在运行时发现,并导致异常。