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

深入理解 Java 反射系列:

Field 成员变量的介绍

每个成员变量有类型

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

获取变量的类型

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

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

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

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

举个例子:

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

    public static final String CLASS_NAME = "net.sxkeji.shixinandroiddemo2.test.reflection.FieldSpy";

    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName(CLASS_NAME);
            Field[] fields = aClass.getFields();
            for (Field field : fields) {
                printFormat("Field:%s \n",field.getName());
                printFormat("Type:\n  %s\n", field.getType().getCanonicalName());
                printFormat("GenericType:\n  %s\n", field.getGenericType().toString());
                printFormat("\n\n");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

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

获取成员变量的修饰符

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

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

类似获取 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;

    public static void main(String[] args) {
        People shixin = new People();
        String fmt = "%6s:  %s  = %s \n";
        Class<? extends People> cls = shixin.getClass();
        try {
            Field idCarNumber = cls.getDeclaredField("idCarNumber");
            System.out.format(fmt, "before", idCarNumber.getName(), shixin.idCarNumber);
            idCarNumber.setLong(shixin, 123456);
            System.out.format(fmt, "after", idCarNumber.getName(), shixin.idCarNumber);

            Field name = cls.getDeclaredField("name");
            System.out.format(fmt, "before", name.getName(), Arrays.asList(shixin.name));
            name.set(shixin, new String[]{"hei", "hei"});
            System.out.format(fmt, "after", name.getName(), Arrays.asList(shixin.name));

            Field habit = cls.getDeclaredField("habit");
            System.out.format(fmt, "before", habit.getName(), shixin.habit);
            habit.set(shixin, MyHabit.LOL);
            System.out.format(fmt, "after", habit.getName(), shixin.habit);


        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

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 :无法转换类型导致的 java.lang.IllegalArgumentException

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

public class FieldTrouble extends BaseTestClass {
    public Integer value;

    public static void main(String[] args) {
        FieldTrouble fieldTrouble = new FieldTrouble();
        Class<? extends FieldTrouble> cls = fieldTrouble.getClass();
        try {
            Field value = cls.getField("value");
            value.setInt(fieldTrouble, 23);

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
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)

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

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

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

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

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

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

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

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

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

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

举个例子:

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

    public static void main(String[] args) {
        FieldTrouble fieldTrouble = new FieldTrouble();
        Class<? extends FieldTrouble> cls = fieldTrouble.getClass();
        try {
//            Field value = cls.getField("value");
//            value.setInt(fieldTrouble, 23);
//            value.set(fieldTrouble, new Integer(23));

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

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}```

上面的代码想要通过反射修改 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)




<div class="se-preview-section-delimiter"></div>

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

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

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

总结

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

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

int x = 1;
x = 2;
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 条评论
登录 后参与评论

相关文章

来自专栏微信公众号:Java团长

Java反射机制深入详解

  反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取他所有的成员变量和...

7410
来自专栏阮一峰的网络日志

Ramda 函数库参考教程

学习函数式编程的过程中,我接触到了 Ramda.js。 我发现,这是一个很重要的库,提供了许多有用的方法,每个 JavaScript 程序员都应该掌握这个工具。...

92680
来自专栏C/C++基础

C++11 变参模板

版权声明:感谢您对博文的关注!校招与社招,有需要内推腾讯的可以QQ(1589276509)or 微信(louislvlv)联系我哈,期待您的加入。 ...

45520
来自专栏彭湖湾的编程世界

【java】java反射初探 ——“当类也学会照镜子”

反射的作用 开门见山地说说反射的作用 1.为我们提供了全面的分析类信息的能力 2.动态加载类 我理解的“反射”的意义 (仅个人理解哈) 我理解的java反射机制...

273100
来自专栏代码拾遗

反射基础之Class

Java中每个类型要么是引用类型,要么是原生类型。类,枚举,数组(他们都继承于java.lang.Object)和接口都是引用类型。例如:java.lang.S...

11540
来自专栏柠檬先生

你不知道的javaScript笔记(4)

类型: JavaScript 有7种内置类型 空值 (null) 未定义(undefined) 布尔值(boolean) 数字(number) 字符串(stri...

23050
来自专栏前端儿

ES6笔记(6)-- Set、Map结构和Iterator迭代器

JS中的iterator也有类似的功能,JS内部为一些数据结构实现了iterator迭代器的接口,让我们可以方便的使用

21210
来自专栏微信公众号:Java团长

Java反射初探 ——“当类也学会照镜子”

镜子(反射机制)照出(反射)了人的全貌(类的全方位的信息,例如方法,成员变量和构造器等的相关信息)

8230
来自专栏WebDeveloper

跟我学习php数组常用函数-下篇

如果是递归的,结果:array('hobby' => array('a' => 'ping-pong', 'b' => 'basketball'));

10920
来自专栏风口上的猪的文章

.NET面试题系列[11] - IEnumerable<T>的派生类

ICollection<T>继承IEnumerable<T>。在其基础上,增加了Add,Remove等方法,可以修改集合的内容。IEnumerable<T>的直...

12620

扫码关注云+社区

领取腾讯云代金券