这些Java教程是针对JDK 8编写的。本页中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
请参阅Java语言更改,了解Java SE 9及其后续版本中更新的语言功能的摘要。
请参阅JDK发行说明,了解所有JDK版本的新功能、增强功能以及已删除或弃用选项的信息。
以下是开发人员常遇到的一些问题,包括解释为什么会出现这些问题以及如何解决它们。
示例会生成一个 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
聪明的读者可能会注意到,如果之前展示的
示例用于获取非公共字段的信息,它将会失败: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()
方法。
如果试图获取或设置一个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); // 解决方案 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()
才能成功。