Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
以下是开发人员遇到的一些常见问题,解释了为什么会发生以及如何解决这些问题。
示例将生成 FieldTroubleIllegalArgumentException。调用 Field.setInt() 将基本类型值,赋值给引用类型 Integer 的字段。在非反射等价物 Integer val = 42 中,编译器将基本类型 42 转换为(或者称为 box (装箱))为引用类型为 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
// production code should handle these exceptions more gracefully
} 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 对象表示的类、枚举或接口的 public 成员字段。要获取 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); // solution
f.setBoolean(ft, Boolean.FALSE); // IllegalAccessException
// production code should handle these exceptions more gracefully
} 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() 才会成功。