解析Java中的Field类和Method类
Field类
Field类中定义了一些方法,可以用来查询字段的类型以及设置或读取字段的值。将这些方法与继承而来的member方法结合在一起.就可以使我们能够找出有关字段声明的全部信息,并且能够操纵某个特定对象或类的字段。
getGenericType方法返回表示字段的声明类型的Type实例。对于像String或int这样的平凡类型,该方法将返回与其相关联的Class对象,例如String.class和int.classo对于像List<String>这样的参数化类型,该方法将返回Parameterizedrype的实例,例如,对像T这样的类型,该方法将返回Typevariable实例。
遗留下来的getType方法将返回字段的类型的Class对象。对于平凡类型,该方法的行为与getGenericType方法的相同。如果字段的声明类型是参数化类型,那么getType方法将返回参数化类型的擦除所对应的Class对象,即原始类型的Class对象。例如,对于声明为List<String>的对象,getType将返回LiSt.class的。如果字段的声明类型是类型变量,那么getType方法将返回类型变量的擦除所对应的class对象。例如,假设有一个类FOO<丁>,对于其声明为T类型的字段,get丁ype将返回object.
class对象。如果FOO被声明为FOo<下extendsNumber>,那么get下ype将返回Number.class.
我们可以使用isEnumConstant方法查询一个字段是否是枚举常量,也可以使用get和set方法来获取和设置字段的值。这些接受object引元并返回Object值的方法都有一种通用形式,以及一些可以直接处理基本类型的更加特化的形式。所有这些方法都要接受一个引元,用来指定所要操作的对象。对于静态字段,将忽略这个对象引元,所以此时也可以将其设
置为null。下面的方法将打印一个对象的short型字段的值:
publicstaticvoidprintShortField(Objecto,Stringname) throwsNoSuchFieldException,IllegalAccessException { Fieldfield=o.getClass().getField(name); shortvalue=(Short)field.get(o); System.out.println(value);
get方法的返回值可以是这个字段所引用的任何对象,如果该字段是基本类型,那么该方法将返回恰当类型的包装器类对象。对于我们的”hort型字段,get方法将返回包含该字段值的short类型的对象,而在将它赋值给本地变量value时,该对象值会自动进行拆箱转换。
set方法的使用也是类似的。将short型字段设置为所提供的值的方法看起来可能像下面这样:
publicstaticvoi setShortField(Objecto,Stringname,shortnv) throwsNoSuchFieldException,IllegalAccessException Fieldfield=0.getClass().getField(name); field.set(o.nv);
虽然set接受的是Object类型的参数,但是我们可以直接传递一个short型的值,并用包装转换将其包装为short类型的对象。
在上面的方法中,如果指定对象的域是不可访问的,并且这种访问权限控制是强制执行的,那么就会抛出IllegalACcessException异常;如果传递的对象与该域的类型不同,就会抛出illegalArgumentException异常;如果该域是非静态的且传递的对象引用是null,就会抛出NullPointerException异常;访问静态域可能会要求对类进行初始化,所以该方法也会抛出ExceptionInInitializerError异常。
Field类还有特定的用来获取和设置基本类型的方法,例如,我们可以在Field对象上调用getPrimitive7ype和setPrimitive7ype,其中Primitive7ype是(首字母大写的)基本类型名。get方法可用于下面的语句:
shortvalue=field.getshort(o);
而set方法可用于下面的语句:
field.setshort(o,nv);
用以上两种方式声明的语句中可以避免使用包装器类对象。
Field类实现了AnnotatedElement接口,所以我们也可以像16.2节那样查询应用于域
上的注解。
凭借上面介绍的方法,我们可以将Field对象用作操纵任意值的一种方式,但是我们应该尽量避免使用它。因为Java语言会在程序的编译期尽可能多地捕获编程错误,所以在我们编写代码时,使用的诸如「ield对象这样的间接方法越少,那么在将它们编译成代码之前,就可以防止更多的错误。而且,我们可以看到,在前面的代码中,要想知道到底会发生什么,与在普通的语法中直接使用域名的情况相比,我们花费在阅读代码上的精力显然大了许多。
Final字段
在通常情况下,对声明为final的字段进行设置将会导致抛出IllegalACcessException
异常,这是我们所能预期的,因为final字段的值是永远不会改变的。但是有些特殊情况—例如在定制的反序列化(见20.8.4节)中,改变final字段的值就是有意义的,我们只有在实例字段上才能通过反射实现这一点,并且前提是在该Field对象上已经调用过了setAccessible(true)。注意,可以成功调用setAccessible(true)是不够的,必须确实调用过它。
这种能力是为高度特化的上下文提供的,并非用于通用目的,我们介绍它仅仅是为了保持内容的完整性。如果脱离了特定的上下文,例如定制的反序列化,那么改变final字段的值可能会导致意外的甚至是灾难性的后果。在这些上下文之外,不能保证对final字段的改变是可见的。即便是在这样的上下文中,在使用这项技术编码时也必须保证安全机制不会阻碍代码的执行。改变值为常量变量(见2.2.3节)的final字段将会导致此改变不可见,除非通过使用反射来实现这种修改。
Method类
method类和它从member类继承而来的方法使得我们可以获得方法声明的完整信息:
"publicTypegetGenericReturnTypeO:该方法返回的是目标方法的返回类型的Type对象。如果目标方法被声明为返回void,则该方法返回void.classo
"publicType[]getGenericParameterTypes():该方法返回目标方法所有参数类型的Type对象数组,这些Type对象将按照参数的声明顺序存储于在数组中。如果目标方法没有任何参数,则该方法返回一个空数组。
.publicType[]getGenericacceptionTypesQ:该方法返回在throws子句中列出的所有异常类型的Type对象数组,这些Type对象将按照异常的声明顺序存储在数组中。
如果目标方法没有声明任何异常,则该方法返回一个空数组。
Java还提供了getReturnType,getParameterTypes和getExceptionTypes方法,用来返回Clas”对象而不是Type对象。就像在使用Field.getType时,参数化类型和类型变量是由它们的擦除所对应的Class对象表示的。
method类实现了AnnotatedElement,并且我们可以像16.2节所讨论的那样去查询应用于方法上的注解。另外,Method类还提供了getParameterAnnotations,用来提供对应用于方法参数上的注解进行访问。getParameterAnnotations方法可以返回Annotation数组,其中最外层数组的每一个元素都与方法的参数相对应;如果某个参数没有任何注解,则该方法为这个参数返回一个长度为0的Annotation数组。如果method对象所表示的方法自身就是一个注解元素,那么getDefaultvalue方法将返回一个表示该元素默认值的Object对象;如果method对象本身不是注解元素或者它没有默认值,则该方法将返回null.Method类也实现了GenericDeclaration,因此定义了getTypeParameters方法,该方法将返回一个Typevariable对象数组。如果给定的method对象表示的不是泛型方法,该方法将返回一个空数组。
我们可以使用isvarArgs方法来检查某个method对象是否是一个可变引元方法,而isBridge方法可以用来检查它是否是一个桥接方法
Method对象最有趣的用法就是反射地调用它自己:
.publicobjectinvoke(objectonThis,object…args)throwsIllegalACcessException,IllegalArgumentException,工nvocation下argetException:该方法在onThis对象上调用method对象定义的方法,并用args的值来设置被调用方法的参数。对于非静态方法,onThis的实际类型就确定了将要调用方法的哪种实现,而对于静态方法,onThis会被忽略,并且通常会设置为null.args值的数量必须和被调用方法的实际参数数量相同,并且这些值的类型必须全部都可赋值给那些被调用方法的参数;否则,我们将会得到工llegalArgumentException异常。请注意,可变引元方法的最后一个参数是一个数组,所以我们必须用实际想要传递的“可变”引元来填充该数组。如果我们想调用我们没有访问权限的方法,该方法就会抛出IllegalACcessException异常。如果被调用方法不是on下his对象的方法,该方法会抛出工llegalArgumentException异常。如果onThis为null并且是非静态的,该方法就会抛出NO1PointerException异常。如果这个method对象表示的是静态方法,并且声明这个静态方法的类仍处于待初始化状态,该方法就会抛出ExceptionIn工nitializerError异常。如果被调用法出异幂,谈万法就会抛出InvocationTargetException异常。
当我们使用invoke方法时,可以直接传递基本类型,也可以使用合适的包装器类。包装器类表示的类型必须可赋值给方法所声明的参数类型。我们可以使用Long,Float或Double来包装double类型的引元,但是不能用Double来包装long或float类型的引元,因为double不是可赋值给long或们oat的。对invoke方法返回的object的处理方法和Field.get一样,都是返回对应于它们的包装器类的基本类型。如果方法声明为void,invoke方法将返回null,
简单地说,就是我们在用invoke来调用方法时,只能使用在Java语言中合法的与其参数
具有相同类型和值的引元。例如,下面的调用
returnstr.indexof(".”,8);
可以用反射写成如下形式:
Throwablefa们ure; try{ MethodindexM=String.class. getMethod("index0f",String.class,int.class); return(Integer)indexM.invoke(str,”,”,8); }catch(NoSuchMethodExceptione){ failure=e; }catch(InvocationTargetExceptione){ fa们ure=e.getCause(); }catch(IllegalAccessExceptione){ failure=e; } throwfa们ure;
虽然编译器对于直接调用所做的安全性检查,在使用反射的情况下,只能在运行时使用invoke时进行,但是基于反射的代码确实拥有与直接调用的代码在语义上等效的安全性检查。访问权限检查可能会以略为不同的方式执行—安全管理器可能会拒绝访问我们的包中的某个方法,即使我们可以直接调用该方法。
当我们可以使用这种形式的调用时,我们有充分的理由去避免它。但是如果我们在编写调试器或其他需要将用户输入解释为对对象操作的泛型应用时使用invoke或get/set方法,就会显得很合理。method对象在某种程度上可以当作类似其他语言中的方法指针来使用,但是我们有更好的工具,尤其是接口、抽象类和嵌套类,可以用来处理那些通常在其他语言中用方法指针解决的问题。