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