文档

Java™ 教程-Java Tutorials 中文版
故障排除
Trail: The Reflection API
Lesson: Members
Section: Fields

故障排除

以下是开发人员遇到的一些常见问题,解释了为什么会发生以及如何解决这些问题。

由于不可转换类型导致的 IllegalArgumentException

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

非公共字段的 NoSuchFieldException

精明的读者可能会注意到,如果前面显示的 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() 方法。

修改 final 字段时出现 IllegalAccessException

如果尝试获取或设置 private 或其他不可访问字段的值,或设置 final 字段(无论其访问修饰符如何)的值,则可能抛出 IllegalAccessException

FieldTroubleToo 示例说明了尝试设置 final 字段时产生的堆栈跟踪类型。


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() 才会成功。

Previous page: Getting and Setting Field Values
Next page: Methods