Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
以下是开发人员遇到的一些常见问题,解释了为什么会发生以及如何解决这些问题。
示例将生成 FieldTrouble
IllegalArgumentException
。调用 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()
才会成功。