文档

Java™ 教程
隐藏目录
故障排除
路径:反射API
课程:成员
章节:字段

故障排除

以下是开发人员常遇到的一些问题,包括解释为什么会出现这些问题以及如何解决它们。

由于不可转换的类型而引起的IllegalArgumentException

FieldTrouble 示例会生成一个 IllegalArgumentException。调用 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

非公共字段的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对象所表示的类、枚举或接口中的成员。要检索在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);  // 解决方案
	    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()才能成功。

上一页:获取和设置字段值
下一页:方法