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

旧城等待, 2022-06-03 01:26 438阅读 0赞
  1. Thanks

Field 成员变量的介绍

每个成员变量有类型和值。

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

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

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

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

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

举个例子:

public class FieldSpy extends BaseTestClass {
public boolean[][] b = { {true, true}, {false, false}};
public String name = “shixinzhang”;
public Integer integer = 23;
public T type;

  1. public static final String CLASS_NAME = "net.sxkeji.shixinandroiddemo2.test.reflection.FieldSpy";
  2. public static void main(String[] args) {
  3. try {
  4. Class<?> aClass = Class.forName(CLASS_NAME);
  5. Field[] fields = aClass.getFields();
  6. for (Field field : fields) {
  7. printFormat("Field:%s \n",field.getName());
  8. printFormat("Type:\n %s\n", field.getType().getCanonicalName());
  9. printFormat("GenericType:\n %s\n", field.getGenericType().toString());
  10. printFormat("\n\n");
  11. }
  12. } catch (ClassNotFoundException e) {
  13. e.printStackTrace();
  14. }
  15. }

}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24

运行结果:

Field:b //二维的布尔值数组
Type:
boolean[][]
GenericType:
class [[Z

Field:name
Type:
java.lang.String
GenericType:
class java.lang.String

Field:integer
Type:
java.lang.Integer
GenericType:
class java.lang.Integer

Field:type //泛型 T 类型,运行时被擦除为 Object
Type:
java.lang.Object
GenericType:
T

Field:CLASS_NAME
Type:
java.lang.String
GenericType:
class java.lang.String

Process finished with exit code 0

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37

获取成员变量的修饰符

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

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

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

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

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

举个例子:

enum MyHabit {
LOL,
CODE
}

public class People extends BaseTestClass {
public long idCarNumber = 1000000000;
public String[] name = {“shi”, “xin”};
public MyHabit habit = MyHabit.CODE;

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

}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38

运行结果:

before: idCarNumber = 1000000000
after: idCarNumber = 123456
before: name = [shi, xin]
after: name = [hei, hei]
before: habit = CODE
after: habit = LOL

Process finished with exit code 0

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8

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

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

public class FieldTrouble extends BaseTestClass {
public Integer value;

  1. public static void main(String[] args) {
  2. FieldTrouble fieldTrouble = new FieldTrouble();
  3. Class<? extends FieldTrouble> cls = fieldTrouble.getClass();
  4. try {
  5. Field value = cls.getField("value");
  6. value.setInt(fieldTrouble, 23);
  7. } catch (NoSuchFieldException e) {
  8. e.printStackTrace();
  9. } catch (IllegalAccessException e) {
  10. e.printStackTrace();
  11. }
  12. }

}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17

Exception in thread “main” java.lang.IllegalArgumentException: Can not set java.lang.Integer field net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.value to (int)23
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:191)
at sun.reflect.UnsafeObjectFieldAccessorImpl.setInt(UnsafeObjectFieldAccessorImpl.java:114)
at java.lang.reflect.Field.setInt(Field.java:949)
at net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.main(FieldTrouble.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11

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

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

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

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

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

  1. 1

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

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

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

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

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

举个例子:

public class FieldTrouble extends BaseTestClass {
public Integer value;
public final boolean wannaPlayGame = true;

  1. public static void main(String[] args) {
  2. FieldTrouble fieldTrouble = new FieldTrouble();
  3. Class<? extends FieldTrouble> cls = fieldTrouble.getClass();
  4. try {

// Field value = cls.getField(“value”);
// value.setInt(fieldTrouble, 23);
// value.set(fieldTrouble, new Integer(23));

  1. Field wannaPlayGame = cls.getDeclaredField("wannaPlayGame");

// wannaPlayGame.setAccessible(true); //加上这句就没问题
wannaPlayGame.setBoolean(fieldTrouble, false);
System.out.format(“Field:%s %s\n”, wannaPlayGame.getName(),fieldTrouble.wannaPlayGame);

  1. } catch (NoSuchFieldException e) {
  2. e.printStackTrace();
  3. } catch (IllegalAccessException e) {
  4. e.printStackTrace();
  5. }
  6. }

}“`

上面的代码想要通过反射修改 wannaPlayGame 变量,运行后会报错:

“`java
java.lang.IllegalAccessException: Can not set final boolean field net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.wannaPlayGame to (boolean)false
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:84)
at sun.reflect.UnsafeQualifiedBooleanFieldAccessorImpl.setBoolean(UnsafeQualifiedBooleanFieldAccessorImpl.java:96)
at java.lang.reflect.Field.setBoolean(Field.java:801)
at net.sxkeji.shixinandroiddemo2.test.reflection.FieldTrouble.main(FieldTrouble.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

发表评论

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

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

相关阅读