Java反射-成员变量
2018-10-17 | 分类 Programing-language | 标签 Java Reflection

获取Field

Java的反射API提供了四个获取类中成员变量的方法。通过这些方法,可以获取类中定义的所有成员变量以及其父类中定义的成员变量。

public Field[] getFields() throws SecurityException {}
public Field[] getDeclaredFields() throws SecurityException {}
public Field getField(String name) throws NoSuchFieldException, SecurityException {}
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException {}

getFields()

getFields()方法返回类或接口中定义的所有public成员变量以及从继承的父类和实现的接口中继承的所有public成员变量。如果对象是一个原始类型、void或者是数组类型,则返回长度为0的数组,数组中的成员变量的顺序是无序的。

// Super
public class Super {
    private Integer privateSuperInt;
    protected Integer protectedSuperInt;
    Integer defaultSuperInt;
    public Integer publicSuperInt;
    private static Integer privateStaticSuperInt;
    protected static Integer protectedStaticSuperInt;
    static Integer defaultStaticSuperInt;
    public static Integer publicStaticSuperInt;
}

// Interface
public interface Interface {
    Integer interfaceInt = 10;
}

// Main 
public class Main extends Super implements Interface {
    public static Integer staticPublicInt;
    private static Integer staticPrivateInt;
    protected static Integer staticProtectedInt;
    static Integer staticDefaultInt;

    public Integer publicInt;
    private Integer privateInt;
    protected Integer protectedInt;
    Integer defaultInt;

    public static void main(String ...args) throws Exception {
        for (Field field : Main.class.getFields()) {
            System.out.println(field.getName());
        }
    }
}

输出结果:

$ java Main
staticPublicInt
publicInt
interfaceInt
publicSuperInt
publicStaticSuperInt

可以看到,getFields()方法返回所有public成员变量,包括静态成员变量。

getField()

getField()方法支持通过成员变量名称获取声明为public的成员变量和从继承的父类或实现的接口中继承的public成员变量。如果成员变量不存在或者成员变量的访问级别不是public则抛出 NoSuchFieldException 异常。对于数组类型,getField()方法获取不到length成员变量的值。

getField()方法在查询成员变量的时候,按照下面的流程进行搜索:

  1. 搜索当前类中定义的所有访问级别是public的成员变量。
  2. 搜索当前类实现的接口中定义的所有public成员变量,按照定义接口的顺序进行搜索。
  3. 如果有父类,则在父类中重复执行 1~3 这三个步骤。

一个例子:

public class Main {
    public Integer publicInt;
    private Integer privateInt;

    public static void main(String ...args) throws Exception {
        System.out.println(Main.class.getField("publicInt").getName());
        System.out.println(Main.class.getField("privateInt").getName());
    }
}

输出结果:

$ java Main
publicInt
Exception in thread "main" java.lang.NoSuchFieldException: privateInt
  at java.lang.Class.getField(Class.java:1703)
  at org.foo.Main.main(Main.java:18)

可以看到,在获取私有成员变量的时候抛出了 NoSuchFieldException 异常。

getDeclaredFields()

getDeclaredFields()方法获取类或接口中声明的所有成员变量的数组,包括非public的成员变量。返回的数组中成员变量的位置是无序的。如果类或接口中未定义任何成员变量,则返回长度为0的数组。如果class对象是一个原始类型、void类型或者是数组类型,则该方法返回的成员数组长度也为0。

public class Main {
    public static Integer staticPublicInt;
    private static Integer staticPrivateInt;
    protected static Integer staticProtectedInt;
    static Integer staticDefaultInt;

    public Integer publicInt;
    private Integer privateInt;
    protected Integer protectedInt;
    Integer defaultInt;

    public static void main(String ...args) throws Exception {
        for (Field field : Main.class.getDeclaredFields()) {
            System.out.println(field.getName());
        }
    }
}

输出结果:

$ java Main
staticPublicInt
staticPrivateInt
staticProtectedInt
staticDefaultInt
publicInt
privateInt
protectedInt
defaultInt

getDeclaredField()

getDeclaredField()方法支持通过成员变量的名称获取类或接口中定义的所有成员变量的Field对象,包括非public成员变量。如果成员变量不存在,则抛出 NoSuchFieldException 异常。对于数组类型,不能通过getDeclaredField()获取 length 成员的Field对象。

public class Main {
    public static Integer staticPublicInt;
    private static Integer staticPrivateInt;
    protected static Integer staticProtectedInt;
    static Integer staticDefaultInt;

    public Integer publicInt;
    private Integer privateInt;
    protected Integer protectedInt;
    Integer defaultInt;

    public static void main(String ...args) throws Exception {
        System.out.println(Main.class.getDeclaredField("defaultInt").getName());
        System.out.println(Main.class.getDeclaredField("unknownField"));
    }
}

输出结果:

$ java Main
defaultInt
Exception in thread "main" java.lang.NoSuchFieldException: unknownField
  at java.lang.Class.getDeclaredField(Class.java:2070)
  at org.foo.Main.main(Main.java:18)

获取Field信息

上面提到的四个方法获取到类中定义的成员变量的信息,这些信息通过 java.lang.reflect.Field 表示。通过处理Field对象,可以获取成员变量的信息。

getName()

通过getName()方法可以获取成员变量的名称,成员的名称就是声明该成员时定义的变量名。

解析修饰符

通过getModifiers()方法可以获取成员变量的修饰符。成员变量的修饰符包括:

  1. 访问修饰符:publicprotectedprivate
  2. 运行时行为相关的修饰符:transientvolatile
  3. 静态修饰符:static;只读修饰符:final 等等

getModifiers()的返回值是一个int类型的数字。通过整数的不同位(bit)来表示不同的修饰符。每一位表示的含义定义在 java.lang.reflect.Modifier 类中。可以通过Modifier中定义的一些is*()方法来解析修饰符。

public class Main {
    public static Integer staticPublicInt;
    private static Integer staticPrivateInt;
    protected static Integer staticProtectedInt;
    static Integer staticDefaultInt;

    public Integer publicInt;
    private Integer privateInt;
    protected Integer protectedInt;
    Integer defaultInt;

    public static void main(String ...args) throws Exception {
        Field field = Main.class.getDeclaredField("staticPublicInt");
        int modifiers = field.getModifiers();

        System.out.println(Modifier.toString(modifiers));
        System.out.println(Modifier.isPublic(modifiers));
        System.out.println(Modifier.isStatic(modifiers));
        System.out.println(Modifier.isFinal(modifiers));
    }
}

输出结果:

$ java Main
public static
true
true
false

获取定义成员变量的类

通过Field.getDeclaringClass()方法可以获取定义该成员变量的类的类对象。

public class Main {
    public static void main(String ...args) throws Exception {
        Field valueField = Foo.class.getDeclaredField("value");
        Class clazz = valueField.getDeclaringClass();
        System.out.println(clazz.getName().equals(Foo.class.getName()));
    }
}

输出结果:

$ java Main
true

赋值

获取了成员变量的Field对象以后,通过一系列set*()方法可以绕过setter方法对成员变量进行赋值。对于Java中的原始类型,Field 类中定义了一系列的set*()方法,用于设置原始类型的值。

public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException {}
public void setByte(Object obj, byte value) throws IllegalArgumentException, IllegalAccessException {}
public void setBoolean(Object obj, boolean value) throws IllegalArgumentException, IllegalAccessException {}
public void setShort(Object obj, short value) throws IllegalArgumentException, IllegalAccessException {}
public void setInt(Object obj, int value) throws IllegalArgumentException, IllegalAccessException {}
public void setLong(Object obj, long value) throws IllegalArgumentException, IllegalAccessException {}
public void setChar(Object obj, char value) throws IllegalArgumentException, IllegalAccessException {}
public void setFloat(Object obj, float value) throws IllegalArgumentException, IllegalAccessException {}
public void setDouble(Object obj, double value) throws IllegalArgumentException, IllegalAccessException {}

使用Field设置值的一个例子:

public class Main {
    public Integer publicInt;

    public static void main(String ...args) throws Exception {
        Main Main = new Main();
        Field field = Main.class.getDeclaredField("publicInt");
        System.out.println("old publicInt = " + Main.publicInt);

        field.set(Main, 10);
        System.out.println("new publicInt = " + Main.publicInt);
    }
}

输出结果:

$ java Main
old publicInt = null
new publicInt = 10

取值

Field 类中定义了一个get()方法,可以获取成员变量的值。对于原始类型,Field 类中提供了一些列get*()方法,用来获取原始类型的值。

public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public boolean getBoolean(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public byte getByte(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public char getChar(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public short getShort(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public int getInt(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public long getLong(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public float getFloat(Object obj) throws IllegalArgumentException, IllegalAccessException {}
public double getDouble(Object obj) throws IllegalArgumentException, IllegalAccessException {}

一个例子:

public class Main {
    public int publicIntInited = 10;
    public Integer publicInteger = 10;

    public static void main(String ...args) throws Exception {
        Main Main = new Main();
        Field field = Main.class.getDeclaredField("publicIntInited");
        System.out.println("publicIntInited = " + field.getInt(Main));

        field = Main.class.getDeclaredField("publicInteger");
        System.out.println("publicInteger = " + field.getInt(Main));
    }
}

输出结果:

$ java Main
publicIntInited = 10
Exception in thread "main" java.lang.IllegalArgumentException: Attempt to get java.lang.Integer field "org.foo.Main.publicInteger" with illegal data type conversion to int
	at sun.reflect.UnsafeFieldAccessorImpl.newGetIllegalArgumentException(UnsafeFieldAccessorImpl.java:69)
	at sun.reflect.UnsafeFieldAccessorImpl.newGetIntIllegalArgumentException(UnsafeFieldAccessorImpl.java:132)
	at sun.reflect.UnsafeObjectFieldAccessorImpl.getInt(UnsafeObjectFieldAccessorImpl.java:57)
	at java.lang.reflect.Field.getInt(Field.java:574)
	at org.foo.Main.main(Main.java:15)

从输出中可以看到,对于 Integer 这种包装类型,通过getInt()方法获取成员变量的值会导致抛出 IllegalArgumentException 异常。可以看到get()方法并不会进行解包操作。

访问私有成员

如果成员变量是私有的,默认在Field上调用get或者set方法时会抛出 IllegalAccessException 异常。

// Foo
public class Foo {
    private int value = 10;
}

// Main
public class Main {
    public static void main(String ...args) throws Exception {
        Foo foo = new Foo();
        Field valueField = Foo.class.getDeclaredField("value");
        System.out.println(valueField.get(foo));
    }
}

输出结果:

$ java Main
Exception in thread "main" java.lang.IllegalAccessException: Class org.foo.Main can not access a member of class org.foo.Foo with modifiers "private"
  at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
  at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
  at java.lang.reflect.Field.get(Field.java:390)
  at org.foo.Main.main(Main.java:10)

为了支持通过反射的方式访问私有成员,Field 类中提供了一个设置可见性的方法:setAccessible()。通过调用setAccessible(true)将成员的可见性设置为公共的,借此可以访问私有成员。如果setAccessible()的参数为false,则表示遵循Java的可见性访问控制机制。

public class Main {
    public static void main(String ...args) throws Exception {
        Foo foo = new Foo();
        Field valueField = Foo.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set(foo, 100);
        System.out.println(valueField.get(foo));

        valueField.setAccessible(false);
        System.out.println(valueField.get(foo));
    }
}

输出结果:

$ java Main
100
Exception in thread "main" java.lang.IllegalAccessException: Class org.foo.Main can not access a member of class org.foo.Foo with modifiers "private"
  at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
  at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
  at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
  at java.lang.reflect.Field.get(Field.java:390)
  at org.foo.Main.main(Main.java:14)
TOP