深入理解 Java 反射:Field (成员变量)

痛定思痛。 2022-07-11 07:11 249阅读 0赞

深入理解 Java 反射系列:

  • 深入理解 Java 反射:Class (反射的入口)
  • 深入理解 Java 反射:Field (成员变量)
  • 深入理解 Java 反射:Method (成员方法)

读完本文你将了解到:

    • Field 成员变量的介绍
    • 获取变量的类型
    • 获取成员变量的修饰符

      • 获取和修改成员变量的值
    • 常见错误 1 无法转换类型导致的 javalangIllegalArgumentException
    • 常见错误 2反射非 public 的变量导致的 NoSuchFieldException
    • 常见错误 3 修改 final类型的变量导致的 IllegalAccessException
    • 总结
    • Thanks

Field 成员变量的介绍

每个成员变量有类型

java.lang.reflect.Field 为我们提供了获取当前对象的成员变量的类型,和重新设值的方法。

获取变量的类型

类中的变量分为两种类型:基本类型引用类型

  • 基本类型( 8 种)
  1. * 整数:byte, short, int, long
  2. * 浮点数:float, double
  3. * 字符:char
  4. * 布尔值:boolean
  • 引用类型
  1. * 所有的引用类型都继承自 java.lang.Object
  2. * 类,枚举,数组,接口都是引用类型
  3. * java.io.Serializable 接口,基本类型的包装类(比如 *java.lang.Double*)也是引用类型

java.lang.reflect.Field 提供了两个方法获去变量的类型:

  • Field.getType():返回这个变量的类型
  • Field.getGenericType():如果当前属性有签名属性类型就返回,否则就返回 Field.getType()

举个例子:

  1. public class FieldSpy<T> extends BaseTestClass {
  2. public boolean[][] b = {
  3. {
  4. true, true}, {
  5. false, false}};
  6. public String name = "shixinzhang";
  7. public Integer integer = 23;
  8. public T type;
  9. public static final String CLASS_NAME = "net.sxkeji.shixinandroiddemo2.test.reflection.FieldSpy";
  10. public static void main(String[] args) {
  11. try {
  12. Class<?> aClass = Class.forName(CLASS_NAME);
  13. Field[] fields = aClass.getFields();
  14. for (Field field : fields) {
  15. printFormat("Field:%s \n",field.getName());
  16. printFormat("Type:\n %s\n", field.getType().getCanonicalName());
  17. printFormat("GenericType:\n %s\n", field.getGenericType().toString());
  18. printFormat("\n\n");
  19. }
  20. } catch (ClassNotFoundException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }

运行结果:

  1. Fieldb //二维的布尔值数组
  2. Type
  3. boolean[][]
  4. GenericType:
  5. class [[Z
  6. Fieldname
  7. Type
  8. java.lang.String
  9. GenericType:
  10. class java.lang.String
  11. Fieldinteger
  12. Type
  13. java.lang.Integer
  14. GenericType:
  15. class java.lang.Integer
  16. Fieldtype //泛型 T 类型,运行时被擦除为 Object
  17. Type
  18. java.lang.Object
  19. GenericType:
  20. T
  21. FieldCLASS_NAME
  22. Type
  23. java.lang.String
  24. GenericType:
  25. class java.lang.String
  26. Process finished with exit code 0

获取成员变量的修饰符

成员变量可以被以下修饰符修饰:

  • 访问权限控制符:public, protected, private
  • 限制只能有一个实例的:static
  • 不允许修改的:final
  • 不会被序列化:transient
  • 线程共享数据的一致性:volatile
  • 注解

类似获取 Class 的修饰符,我们可以使用 Field.getModifiers() 方法获取当前成员变量的修饰符。
返回 java.lang.reflect.Modifier 中定义的整形值。

由于 Field 间接继承了 java.lang.reflect.AnnotatedElement ,因此运行时也可以获得修饰成员变量的注解,当然前提是这个注解被 java.lang.annotation.RetentionPolicy.RUNTIME 修饰。

获取和修改成员变量的值

拿到一个对象后,我们可以在运行时修改它的成员变量的值,对运行时来说,反射修改变量值的操作和类中修改变量的结果是一样的。

举个例子:

  1. enum MyHabit {
  2. LOL,
  3. CODE
  4. }
  5. public class People extends BaseTestClass {
  6. public long idCarNumber = 1000000000;
  7. public String[] name = {
  8. "shi", "xin"};
  9. public MyHabit habit = MyHabit.CODE;
  10. public static void main(String[] args) {
  11. People shixin = new People();
  12. String fmt = "%6s: %s = %s \n";
  13. Class<? extends People> cls = shixin.getClass();
  14. try {
  15. Field idCarNumber = cls.getDeclaredField("idCarNumber");
  16. System.out.format(fmt, "before", idCarNumber.getName(), shixin.idCarNumber);
  17. idCarNumber.setLong(shixin, 123456);
  18. System.out.format(fmt, "after", idCarNumber.getName(), shixin.idCarNumber);
  19. Field name = cls.getDeclaredField("name");
  20. System.out.format(fmt, "before", name.getName(), Arrays.asList(shixin.name));
  21. name.set(shixin, new String[]{
  22. "hei", "hei"});
  23. System.out.format(fmt, "after", name.getName(), Arrays.asList(shixin.name));
  24. Field habit = cls.getDeclaredField("habit");
  25. System.out.format(fmt, "before", habit.getName(), shixin.habit);
  26. habit.set(shixin, MyHabit.LOL);
  27. System.out.format(fmt, "after", habit.getName(), shixin.habit);
  28. } catch (NoSuchFieldException e) {
  29. e.printStackTrace();
  30. } catch (IllegalAccessException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }

运行结果:

  1. before: idCarNumber = 1000000000
  2. after: idCarNumber = 123456
  3. before: name = [shi, xin]
  4. after: name = [hei, hei]
  5. before: habit = CODE
  6. after: habit = LOL
  7. Process finished with exit code 0

常见错误 1 :无法转换类型导致的 java.lang.IllegalArgumentException

下面的代码和上个例子的代码很相似,但是运行时却报错:

  1. public class FieldTrouble extends BaseTestClass {
  2. public Integer value;
  3. public static void main(String[] args) {
  4. FieldTrouble fieldTrouble = new FieldTrouble();
  5. Class<? extends FieldTrouble> cls = fieldTrouble.getClass();
  6. try {
  7. Field value = cls.getField("value");
  8. value.setInt(fieldTrouble, 23);
  9. } catch (NoSuchFieldException e) {
  10. e.printStackTrace();
  11. } catch (IllegalAccessException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.Integer field net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.value to (int)23
  17. at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
  18. at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:191)
  19. at sun.reflect.UnsafeObjectFieldAccessorImpl.setInt(UnsafeObjectFieldAccessorImpl.java:114)
  20. at java.lang.reflect.Field.setInt(Field.java:949)
  21. at net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.main(FieldTrouble.java:24)
  22. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  23. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  24. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  25. at java.lang.reflect.Method.invoke(Method.java:498)
  26. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

为什么我们无法给 Integer 类型的 value 使用 setInt() 方法重新设值呢?

这是因为在使用反射获取或者修改一个变量的值时,编译器不会进行自动装/拆箱。

因此我们无法给一个 Integer 类型的变量赋整型值,必须给它赋一个 Integer 对象才可以。

使用 Field.set(Object obj, Object value) 方法解决这个问题:

  1. f.set(ft, new Integer(43));

常见错误 2:反射非 public 的变量导致的 NoSuchFieldException

如果你使用 Class.getField() 或者 Class.getFields() 获取非 public 的变量,编译器会报 java.lang.NoSuchFieldException 错。

下面这张图说明了获取变量的四个方法所左右的对象类型:

shixinzhang

常见错误 3 :修改 final类型的变量导致的 IllegalAccessException

当你想要获取或者修改 不可修改(final)的变量时,会导致IllegalAccessException

举个例子:

  1. public class FieldTrouble extends BaseTestClass {
  2. public Integer value;
  3. public final boolean wannaPlayGame = true;
  4. public static void main(String[] args) {
  5. FieldTrouble fieldTrouble = new FieldTrouble();
  6. Class<? extends FieldTrouble> cls = fieldTrouble.getClass();
  7. try {
  8. // Field value = cls.getField("value");
  9. // value.setInt(fieldTrouble, 23);
  10. // value.set(fieldTrouble, new Integer(23));
  11. Field wannaPlayGame = cls.getDeclaredField("wannaPlayGame");
  12. // wannaPlayGame.setAccessible(true); //加上这句就没问题
  13. wannaPlayGame.setBoolean(fieldTrouble, false);
  14. System.out.format("Field:%s %s\n", wannaPlayGame.getName(),fieldTrouble.wannaPlayGame);
  15. } catch (NoSuchFieldException e) {
  16. e.printStackTrace();
  17. } catch (IllegalAccessException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }```
  22. 上面的代码想要通过反射修改 wannaPlayGame 变量,运行后会报错:
  23. ```java
  24. java.lang.IllegalAccessException: Can not set final boolean field net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.wannaPlayGame to (boolean)false
  25. at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
  26. at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:84)
  27. at sun.reflect.UnsafeQualifiedBooleanFieldAccessorImpl.setBoolean(UnsafeQualifiedBooleanFieldAccessorImpl.java:96)
  28. at java.lang.reflect.Field.setBoolean(Field.java:801)
  29. at net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.main(FieldTrouble.java:28)
  30. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  31. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  32. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  33. at java.lang.reflect.Method.invoke(Method.java:498)
  34. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
  35. <div class="se-preview-section-delimiter"></div>

这是因为在类初始化后,无形之中会有一个访问限制阻止我们修改 final 类型的变量

由于 Field 继承自 AccessibleObject , 我们可以使用 AccessibleObject.setAccessible() 方法告诉安全机制,这个变量可以访问。

因此上面的例子中,声明这个变量是可访问的:wannaPlayGame.setAccessible(true),运行就正常了。

总结

在使用反射修改某个对象的成员变量前你要明白,这样做会造成一定程度的性能开销,因为在反射时这样的操作需要引发许多额外操作,比如验证访问权限等。只在特殊情况下这么做。

另外使用反射也会导致一些运行时的计算优化失效。比如下面的代码,运行时极可能会优化为最后一句:

  1. int x = 1;
  2. x = 2;
  3. x = 3;

但是如果使用反射 Field.set*() 做同样的操作,可能就不会有这种优化了。

使用 setAccessible(true) 方法前也需要注意,这可能会导致意想不到的后果,比如:
在运行时虽然你通过反射修改了变量 a 的值,但其他部分可能还在使用原来的值。

Thanks

http://docs.oracle.com/javase/tutorial/reflect/member/index.html
http://docs.oracle.com/javase/tutorial/reflect/member/fieldTypes.html

发表评论

表情:
评论列表 (有 0 条评论,249人围观)

还没有评论,来说两句吧...

相关阅读