深入学习Java反射之道-field

来源:互联网 发布:js获取div class 编辑:程序博客网 时间:2024/04/17 05:20

深入学习Java反射之道-Field

JAVA 使用 java.lang.reflect.Member接口代表反射使用的类成员。Member成员包含以下几种:

  • java.lang.reflect.Field
  • java.lang.reflect.Method
  • java.lang.reflect.Constructor

反射中的类成员–Field

java.lang.reflect.Filed 提供了对特定对象Field的操作,包括获取类型信息,get和set值。

获取Field的类型

  • Field.getType()

返回field声明类型的类实例。

  • Field.getGenericType()

返回field对应的泛型类型。

示例代码

import java.lang.reflect.Field;import java.util.List;/** * 引用自 * http://docs.oracle.com/javase/tutorial/reflect/member/fieldTypes.html */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());    } catch (ClassNotFoundException x) {        x.printStackTrace();    } catch (NoSuchFieldException x) {        x.printStackTrace();    }    }}

获取Field的修饰符

field的声明中会包含部分修饰符:

  • 权限访问修饰符
    • public
    • protectded
    • private
  • Field专属运行期修饰符
    • 该字段不加入序列化transient
    • 并发写可见 volatile
  • 静态修饰符 static
  • 值不可变修饰符 final
  • 注解

Field.getModifiers()

方法返回不同修饰符的Int值,Modifier类可以用于解析int值。

示例代码

import java.lang.reflect.Field;import java.lang.reflect.Modifier;import static java.lang.System.out;enum Spy { BLACK , WHITE }/** * 查询修饰符对应field * $ java FieldModifierSpy FieldModifierSpy volatile * Fields in Class 'FieldModifierSpy' containing modifiers:  volatile * share    [ synthetic=false enum_constant=false ] * 引用自 * http://docs.oracle.com/javase/tutorial/reflect/member/fieldModifiers.html */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();                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");            }        } 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;    }}

Field.isSynthetic()

isSynthetic()方法可以用来判断field是否是编译器生成的用于内部使用的属性.例如,内部类(不包括静态内部类)中会使用字段this$0指向外部类的对象引用;enums会使用字段$VALUES来实现static方法values()。这些synthetic field被包含在Class.getDeclaredFields()的返回结果中,但不会被包含在Class.getField()中,因为这些synthetic field通常来说不是public的。

内部类this$0
public class OutClass{  public class InnerClass{  }}
执行javac,javap -verbose命令
  • javac

生成OutClass.class,OutClass$InnerClass.class文件

  • javap -verbose
{  final OutClass this$0;  //此处是对外部类对象的引用    descriptor: LOutClass;    flags: ACC_FINAL, ACC_SYNTHETIC  public OutClass$InnerClass(OutClass);    descriptor: (LOutClass;)V    flags: ACC_PUBLIC    Code:      stack=2, locals=2, args_size=2         0: aload_0         1: aload_1         2: putfield      #1                  // Field this$0:LOutClass;         5: aload_0         6: invokespecial #2                  // Method java/lang/Object."<init>":()V         9: return      LineNumberTable:        line 2: 0}

Field值的get和set

对于某个给定的对象,可以使用反射来给对象的属性赋值,这通常发生在无法通过正常方法赋值的场景下。注意,通过反射赋值违反了类的设计初衷,所以使用是应当尽可能的慎重。

示例代码

import java.lang.reflect.Field;import java.util.Arrays;import static java.lang.System.out;enum Tweedle { DEE, DUM }/** * field -> set value * 引用自 * http://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html * $ java Book * BEFORE:  chapters     = 0 *  AFTER:  chapters     = 12 * BEFORE:  characters   = [Alice, White Rabbit] *  AFTER:  characters   = [Queen, King] * BEFORE:  twin         = DEE *  AFTER:  twin         = 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));    } catch (NoSuchFieldException x) {        x.printStackTrace();    } catch (IllegalAccessException x) {        x.printStackTrace();    }    }}

NOTE:
通过反射进行赋值需要一定数量性能资源,用于前置操作,例如验证访问权限。从运行期的角度来看,结果是相同的,并且操作和在代码中修改的原子性相同。
但是使用反射会失去虚拟机对代码的优化,下面就是个例子,虚拟机会优化,反射却不会。

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

需要注意的Exception

IllegalArgumentException

不可转型异常

产生异常的例子
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();    }    }}/** * RESULT: * $ 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) */
原因
  • 对于非反射的表达式Integer val = 42;编译器会将值类型42装箱成包装类new Integer(42)
  • 当使用反射时,编译器不会进行装箱。

要解决上面的问题可以将f.setInt(ft, 42);改为调用Field.set(Object obj, Object value)方法。

IllegalAccessException

无权限修改异常,常见于修改private或final属性

产生异常的例子
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    } 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) */

解决方案: 调用f.setAccessible(true);方法,提供访问权限.

原创粉丝点击