Troubleshooting

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

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));

Tip:

使用反射设置或获取字段时,编译器没有机会进行装箱。它只能转换与Class.isAssignableFrom()规范中所述相关的类型。该示例预计会失败,因为isAssignableFrom()在此测试中将返回false,可以通过编程将其用于验证特定转换是否可行:

Integer.class.isAssignableFrom(int.class) == false

同样,从原始类型到引用类型的自动转换也无法反映出来。

int.class.isAssignableFrom(Integer.class) == false

非公共字段的 NoSuchFieldException

精明的 Reader 可能会注意到,如果使用前面显示的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)

Tip:

Class.getField()Class.getFields()方法返回由Class对象表示的类,枚举或interface的* public *成员字段。要检索Class中声明(但未继承)的所有字段,请使用Class.getDeclaredFields()方法。

IllegalAccessException 修改final字段时

如果try获取或设置private或其他不可访问字段的值或设置final字段的值(无论其访问修饰符如何),都可能引发IllegalAccessException

FieldTroubleToo示例说明了try设置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)

Tip:

存在访问限制,可以防止在初始化类后设置final字段。但是,声明Field扩展为AccessibleObject,这提供了禁止此检查的功能。

如果AccessibleObject.setAccessible()成功,则对该字段值的后续操作将不会失败。这可能会产生意想不到的副作用。例如,有时原始值将被应用程序的某些部分 continue 使用,即使该值已被修改。仅当安全上下文允许该操作时,AccessibleObject.setAccessible()才会成功。