这些Java教程是针对JDK 8编写的。本页中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
请参阅Java语言更改,了解Java SE 9及其后续版本中更新的语言功能的摘要。
请参阅JDK发行说明,了解所有JDK版本的新功能、增强功能以及已删除或弃用选项的信息。
以下是开发人员常遇到的一些问题,包括解释为什么会出现这些问题以及如何解决它们。
示例会生成一个 FieldTroubleIllegalArgumentException。调用 Field.setInt() 来设置一个字段,该字段的引用类型是 Integer,但给定的值是原始类型。在非反射版本的代码中,Integer val = 42,编译器会将原始类型 42 转换(或者称之为装箱)为引用类型 new Integer(42),以使其通过类型检查。而在使用反射时,类型检查只会在运行时发生,所以没有机会进行装箱。
import java.lang.reflect.Field;
public class FieldTrouble {
public Integer val;
public static void main(String... args) {
FieldTrouble ft = new FieldTrouble();
try {
Class<?> c = ft.getClass();
Field f = c.getDeclaredField("val");
f.setInt(ft, 42); // IllegalArgumentException
// 生产代码应该更优雅地处理这些异常
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ java FieldTrouble
Exception in thread "main" java.lang.IllegalArgumentException: Can not set
java.lang.Object field FieldTrouble.val to (long)42
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
(UnsafeFieldAccessorImpl.java:146)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
(UnsafeFieldAccessorImpl.java:174)
at sun.reflect.UnsafeObjectFieldAccessorImpl.setLong
(UnsafeObjectFieldAccessorImpl.java:102)
at java.lang.reflect.Field.setLong(Field.java:831)
at FieldTrouble.main(FieldTrouble.java:11)
为了消除这个异常,应该用以下方式替换有问题的代码行,调用 Field.set(Object obj, Object value):
f.set(ft, new Integer(43));
Class.isAssignableFrom() 规范描述的相关类型之间的转换来执行类型转换。此示例预计会失败,因为 isAssignableFrom() 在这个测试中将返回 false,可以在程序中使用它来验证特定的转换是否可行:
Integer.class.isAssignableFrom(int.class) == false
同样地,反射中也不可能进行从原始类型到引用类型的自动转换。
int.class.isAssignableFrom(Integer.class) == false
聪明的读者可能会注意到,如果之前展示的示例用于获取非公共字段的信息,它将会失败:FieldSpy
$ java FieldSpy java.lang.String count
java.lang.NoSuchFieldException: count
at java.lang.Class.getField(Class.java:1519)
at FieldSpy.main(FieldSpy.java:12)
Class.getField()和Class.getFields()方法返回公共成员字段,这些字段是由Class对象所表示的类、枚举或接口中的成员。要检索在Class中声明(但不是继承)的所有字段,请使用Class.getDeclaredFields()方法。
如果试图获取或设置一个private或其他不可访问的字段的值,或者设置一个final字段的值(无论其访问修饰符如何),可能会抛出IllegalAccessException异常。
示例展示了试图设置final字段时产生的堆栈跟踪类型。FieldTroubleToo
import java.lang.reflect.Field;
public class FieldTroubleToo {
public final boolean b = true;
public static void main(String... args) {
FieldTroubleToo ft = new FieldTroubleToo();
try {
Class<?> c = ft.getClass();
Field f = c.getDeclaredField("b");
// f.setAccessible(true); // 解决方案
f.setBoolean(ft, Boolean.FALSE); // IllegalAccessException
// 生产代码应该更优雅地处理这些异常
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalArgumentException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ java FieldTroubleToo
java.lang.IllegalAccessException: Can not set final boolean field
FieldTroubleToo.b to (boolean)false
at sun.reflect.UnsafeFieldAccessorImpl.
throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:55)
at sun.reflect.UnsafeFieldAccessorImpl.
throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:63)
at sun.reflect.UnsafeQualifiedBooleanFieldAccessorImpl.setBoolean
(UnsafeQualifiedBooleanFieldAccessorImpl.java:78)
at java.lang.reflect.Field.setBoolean(Field.java:686)
at FieldTroubleToo.main(FieldTroubleToo.java:12)
final字段。然而,Field被声明为扩展AccessibleObject,提供了抑制此检查的能力。
AccessibleObject.setAccessible()成功,那么对该字段值的后续操作将不会因此问题而失败。这可能会产生意外的副作用;例如,有时候即使值已经被修改,应用程序的某些部分仍将继续使用原始值。只有在安全上下文允许操作时,AccessibleObject.setAccessible()才能成功。