专栏首页代码拾遗反射基础之Field

反射基础之Field

java.lang.reflect.Field 类的方法可以查询字段的信息。比如:名字,类型,修饰符和注解。同样也有方法可以动态访问和修改字段的值。

获取字段的类型

下面这个例子是如何获取字段的类型和泛型类型:

import java.lang.reflect.Field;
import java.util.List;

public class FieldSpy<T> {
    public boolean[][] b = {{ false, false }, { true, true } };
    public String name  = "Alice";
    public List<Integer> list;
    public T val;

    public static void main(String... args) {
    try {
        Class<?> c = Class.forName(args[0]);
        Field f = c.getField(args[1]);
        System.out.format("Type: %s%n", f.getType());
        System.out.format("GenericType: %s%n", f.getGenericType());

        // production code should handle these exceptions more gracefully
    } catch (ClassNotFoundException x) {
        x.printStackTrace();
    } catch (NoSuchFieldException x) {
        x.printStackTrace();
    }
    }
}

运行结果:

java FieldSpy FieldSpy b
Type: class [[Z
GenericType: class [[Z
$ java FieldSpy FieldSpy name
Type: class java.lang.String
GenericType: class java.lang.String
$ java FieldSpy FieldSpy list
Type: interface java.util.List
GenericType: java.util.List<java.lang.Integer>
$ java FieldSpy FieldSpy val
Type: class java.lang.Object
GenericType: T

b的类型是一个boolean的二维数组,类型名师通过Class.getName()获取的 val的类型是 java.lang.Object 因为Java的类型擦除在编译是会移出泛型信息 Field.getGenericType() 或检查类文件中的信息,如果没有提供则使用Field.getType()。

查询解析字段修饰符

可以修饰字段的修饰符:

  • 访问修饰符:public,protected,private。
  • 影响运行状态行为的:transient,volatile
  • 静态修饰符: static
  • 不可变修饰:final
  • 注解

方法Field.getModifiers() 可以返回其修饰符,修饰符的定义在java.lang.reflect.Modifier。 通过Field.isSynthetic() 检测是否是合成域(synthetic),Field.isEnumConstant()检测是否是枚举常量

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import static java.lang.System.out;

enum Spy { BLACK , WHITE }

public class FieldModifierSpy {
    volatile int share;
    int instance;
    class Inner {}

    public static void main(String... args) {
    try {
        Class<?> c = Class.forName(args[0]);
        int searchMods = 0x0;
        for (int i = 1; i < args.length; i++) {
        searchMods |= modifierFromString(args[i]);
        }

        Field[] flds = c.getDeclaredFields();
        out.format("Fields in Class '%s' containing modifiers:  %s%n",
               c.getName(),
               Modifier.toString(searchMods));
        boolean found = false;
        for (Field f : flds) {
        int foundMods = f.getModifiers();
        // Require all of the requested modifiers to be present
        if ((foundMods & searchMods) == searchMods) {
            out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n",
                   f.getName(), f.isSynthetic(),
                   f.isEnumConstant());
            found = true;
        }
        }

        if (!found) {
        out.format("No matching fields%n");
        }

        // production code should handle this exception more gracefully
    } catch (ClassNotFoundException x) {
        x.printStackTrace();
    }
    }

    private static int modifierFromString(String s) {
    int m = 0x0;
    if ("public".equals(s))           m |= Modifier.PUBLIC;
    else if ("protected".equals(s))   m |= Modifier.PROTECTED;
    else if ("private".equals(s))     m |= Modifier.PRIVATE;
    else if ("static".equals(s))      m |= Modifier.STATIC;
    else if ("final".equals(s))       m |= Modifier.FINAL;
    else if ("transient".equals(s))   m |= Modifier.TRANSIENT;
    else if ("volatile".equals(s))    m |= Modifier.VOLATILE;
    return m;
    }
}

运行输出:

$ java FieldModifierSpy FieldModifierSpy volatile
Fields in Class 'FieldModifierSpy' containing modifiers:  volatile
share    [ synthetic=false enum_constant=false ]

$ java FieldModifierSpy Spy public
Fields in Class 'Spy' containing modifiers:  public
BLACK    [ synthetic=false enum_constant=true  ]
WHITE    [ synthetic=false enum_constant=true  ]

$ java FieldModifierSpy FieldModifierSpy\$Inner final
Fields in Class 'FieldModifierSpy$Inner' containing modifiers:  final
this$0   [ synthetic=true  enum_constant=false ]

$ java FieldModifierSpy Spy private static final
Fields in Class 'Spy' containing modifiers:  private static final
$VALUES  [ synthetic=true  enum_constant=false ]

注意,虽然一些字段在源代码中找不到,但是仍然会被列出,因为编译器会生成一些运行时需要的合成域。检测一个字段是否是合成域只需要执行Field.isSynthetic()即可。合成域一般是编译器独立的,但是一般使用this$0代表内部类。

获取和设置字段值

示例:

import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;

enum Tweedle { DEE, DUM }

public class Book {
    public long chapters = 0;
    public String[] characters = { "Alice", "White Rabbit" };
    public Tweedle twin = Tweedle.DEE;

    public static void main(String... args) {
    Book book = new Book();
    String fmt = "%6S:  %-12s = %s%n";

    try {
        Class<?> c = book.getClass();

        Field chap = c.getDeclaredField("chapters");
        out.format(fmt, "before", "chapters", book.chapters);
        chap.setLong(book, 12);
        out.format(fmt, "after", "chapters", chap.getLong(book));

        Field chars = c.getDeclaredField("characters");
        out.format(fmt, "before", "characters",
               Arrays.asList(book.characters));
        String[] newChars = { "Queen", "King" };
        chars.set(book, newChars);
        out.format(fmt, "after", "characters",
               Arrays.asList(book.characters));

        Field t = c.getDeclaredField("twin");
        out.format(fmt, "before", "twin", book.twin);
        t.set(book, Tweedle.DUM);
        out.format(fmt, "after", "twin", t.get(book));

        // production code should handle these exceptions more gracefully
    } catch (NoSuchFieldException x) {
        x.printStackTrace();
    } catch (IllegalAccessException x) {
        x.printStackTrace();
    }
    }
}

相关输出:

$ java Book
BEFORE:  chapters     = 0
 AFTER:  chapters     = 12
BEFORE:  characters   = [Alice, White Rabbit]
 AFTER:  characters   = [Queen, King]
BEFORE:  twin         = DEE
 AFTER:  twin         = DUM

注意,使用反射设置字段的值的性能非常差,因为需要判断是否可以访问,而且并不会有编译优化。

问题排查
IllegalArgumentException due to Inconvertible Types

下面这个例子是将42设置到Integer的字段,如果非反射会自动的装箱,但是对于反射则需要自己来装箱:

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(fit, new Integer(42))。可以通过 Class.isAssignableFrom()来检查类型。

NoSuchFieldException for Non-Public Fields

对于FiledSpy程序:

$ java FieldSpy java.lang.String count
java.lang.NoSuchFieldException: count
        at java.lang.Class.getField(Class.java:1519)
        at FieldSpy.main(FieldSpy.java:12)
IllegalAccessException when Modifying Final Fields

当访问private的时候,或者private没有设置setAccessible(true) 就会抛出这个异常:

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)

本文分享自微信公众号 - 代码拾遗(gh_8f61e8bcb1b1)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-05-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 反射基础之Method

    一个方法声明包括:方法名,描述符,参数,返回类型和异常。可以通过java.lang.reflect.Method类获取这些信息。 下面的例子说明了如何获取一个类...

    代码拾遗
  • 反射基础之Constructor

    构造器的声明包含了:名字,修饰符,参数和异常。可以通过java.lang.reflect.Constructor类获取这些信息。 下面的例子描述了如何获取构造器...

    代码拾遗
  • ​反射基础之Enum

    因为枚举也是一个类,所以也可以通过Field,Method,Constructor的反射API获取其他信息:

    代码拾遗
  • 常见加载类错误分析

    在执行 Java程序时经常会碰到ClassNotFoundException和NoClassDefFoundError两个异常,它们都和类加载有关,下面详细分一...

    MickyInvQ
  • Maven3问题记录-1

    M3_HOME=D:\ProgramFiles\apache-maven-3.0.3

    py3study
  • Serializable接口心得总结

    可以看到该类的内部实现完全为空,在Java IO体系中仅起一个标记的作用。那么这个标记具体是如何发挥作用的呢?我们测试一下:

    Remember_Ray
  • Serializable接口心得总结

    可以看到该类的内部实现完全为空,在Java IO体系中仅起一个标记的作用。那么这个标记具体是如何发挥作用的呢?我们测试一下:

    Remember_Ray
  • Linux下如何查看JDK安装路径

    使用 echo $JAVA_HOME 命令可以定位到Java安装路径,但是前提是配置了环境变量$JAVA_HOME,否则还是定位不到,如下所示:

    浩Coding
  • Java 常见内存溢出异常与代码实现

    Java 堆 OutOfMemoryError Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间...

    用户1212940
  • Java 11 新特性

    java 11 是继 java8 之后的第一个LTS版本。因此有必要针对它进行一些深入的学习,虽然短时间内java8 还是主流版本。当然,如果从java8基础上...

    pollyduan

扫码关注云+社区

领取腾讯云代金券