引言

这篇文章,我们来看Field这个类的一些重要方法。

get方法

get方法用来获取这个字段在指定对象上的值。

  1. @CallerSensitive
  2. public Object get(Object obj)
  3. throws IllegalArgumentException, IllegalAccessException
  4. {
  5. if (!override) {
  6. if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
  7. Class<?> caller = Reflection.getCallerClass();
  8. checkAccess(caller, clazz, obj, modifiers);
  9. }
  10. }
  11. return getFieldAccessor(obj).get(obj);
  12. }

这个方法首先会校验override这个属性,override我们在分析AccessibleObject时已经详细描述过,它用来表示要不要对字段或者方法进行语言层面上的访问权限控制检查。注意override是指定要不要进行检查,如果不进行检查,那么不管是什么类型(public、private、protected和default)的字段和方法,我们都能通过get方法来获取值,如果要执行检查,最终能不能访问就要看该字段或者方法的修饰符了。看下面的例子:

  1. public class FieldGetTest {
  2. public String aPublicString = "aPublicString";
  3. String aDefaultString = "aDefaultString";
  4. private String aPrivateString = "aPrivateString";
  5. protected String aProtectedString = "aProtectedString";
  6. }
  7. public class FieldTest {
  8. public static void main(String[] args) throws IllegalAccessException {
  9. Class<FieldGetTest> fieldGetTestClass = FieldGetTest.class;
  10. Field[] declaredFields = fieldGetTestClass.getDeclaredFields();
  11. FieldGetTest fieldGetTest = new FieldGetTest();
  12. for (Field declaredField : declaredFields) {
  13. declaredField.setAccessible(true);
  14. System.out.println(declaredField.get(fieldGetTest));
  15. }
  16. }
  17. }

输出:

  1. aPublicString
  2. aDefaultString
  3. aPrivateString
  4. aProtectedString

我们使用get方法去获取FieldGetTest的字段,在获取之前使用setAccessible为true,表示不去进行访问控制权限的检查,结果各种访问权限(public/private/default/protected)都能访问。
我们去掉set方法调用:

  1. public class FieldTest {
  2. public static void main(String[] args) throws IllegalAccessException {
  3. Class<FieldGetTest> fieldGetTestClass = FieldGetTest.class;
  4. Field[] declaredFields = fieldGetTestClass.getDeclaredFields();
  5. FieldGetTest fieldGetTest = new FieldGetTest();
  6. for (Field declaredField : declaredFields) {
  7. System.out.println(declaredField.get(fieldGetTest));
  8. }
  9. }
  10. }

结果,就会报错:

  1. public class FieldTest {
  2. public static void main(String[] args) throws IllegalAccessException {
  3. Class<FieldGetTest> fieldGetTestClass = FieldGetTest.class;
  4. Field[] declaredFields = fieldGetTestClass.getDeclaredFields();
  5. FieldGetTest fieldGetTest = new FieldGetTest();
  6. for (Field declaredField : declaredFields) {
  7. System.out.println(declaredField.getName());
  8. declaredField.get(fieldGetTest);
  9. }
  10. }
  11. }

输出:

  1. aPublicString
  2. aDefaultString
  3. Exception in thread "main" java.lang.IllegalAccessException: Class person.andy.concurrency.reflect.con.FieldTest can not access a member of class person.andy.concurrency.reflect.field.FieldGetTest with modifiers ""
  4. at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
  5. at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
  6. at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
  7. at java.lang.reflect.Field.get(Field.java:390)
  8. at person.andy.concurrency.reflect.con.FieldTest.main(FieldTest.java:14)

报错显示我们不能访问default权限的字段。注意,FieldTest和FieldGetTest这两个类的定义没在一个包下面。访问控制权限跟这两个类的定义位置有关。如果在一个包下面,那么default权限的字段就能被访问。如果我们不是在FieldTest这个类的main方法执行这些代码而是在FieldGetTest的main方法里面,那么即使有访问控制权限的检查,get方法也能正常执行,因为访问控制权限允许FieldGetTest的main方法访问这个类自己定义的各种类型的字段。

set方法

set方法的定义如下:

  1. @CallerSensitive
  2. public void set(Object obj, Object value)
  3. throws IllegalArgumentException, IllegalAccessException
  4. {
  5. if (!override) {
  6. if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
  7. Class<?> caller = Reflection.getCallerClass();
  8. checkAccess(caller, clazz, obj, modifiers);
  9. }
  10. }
  11. getFieldAccessor(obj).set(obj, value);
  12. }

set方法用来设置某个对象的某个字段的值。与get方法类似,set方法同样需要校验override的值,也就是首先看是否需要进行访问控制权限的校验,这个我们不再举例演示。
set方法还需要注意的一点是对final字段的操作,java中final字段只能被赋值一次并且不能再改变。所以当使用反射对final字段赋值时,有一些限制,这里总结一下:
如果是非static的final字段,想通过set方法设置字段值,必须先调用setAccessible(true)来忽略访问控制权限的校验,不管这个字段是哪种权限的(public、private、default、protected)。
如果是static的final字段,无论如何都不能通过set方法来设置该字段的值。
看下面的例子:

  1. public class FieldGetTest {
  2. public final String A_PUBLIC_STRING = "aPublicString";
  3. }
  4. public class FieldTest {
  5. public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
  6. Class<FieldGetTest> fieldGetTestClass = FieldGetTest.class;
  7. Field thePublicFinalString = fieldGetTestClass.getDeclaredField("A_PUBLIC_STRING");
  8. FieldGetTest fieldGetTest = new FieldGetTest();
  9. thePublicFinalString.set(fieldGetTest,"newPublicFinalString");
  10. System.out.println(thePublicFinalString.get(fieldGetTest));
  11. }
  12. }

FieldGetTest中有一个非静态变量A_PUBLIC_STRING,然后直接通过set方法去设置这个字段的值,结果报错:

  1. Exception in thread "main" java.lang.IllegalAccessException: Can not set final java.lang.String field person.andy.concurrency.reflect.field.FieldGetTest.A_PUBLIC_STRING to java.lang.String
  2. at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
  3. at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
  4. at sun.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:79)
  5. at java.lang.reflect.Field.set(Field.java:764)
  6. at person.andy.concurrency.reflect.con.FieldTest.main(FieldTest.java:12)

报错显示不能设置final字段。
我们加上setAccessible()的调用:

  1. public class FieldTest {
  2. public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
  3. Class<FieldGetTest> fieldGetTestClass = FieldGetTest.class;
  4. Field thePublicFinalString = fieldGetTestClass.getDeclaredField("A_PUBLIC_STRING");
  5. thePublicFinalString.setAccessible(true);
  6. FieldGetTest fieldGetTest = new FieldGetTest();
  7. thePublicFinalString.set(fieldGetTest,"newPublicFinalString");
  8. System.out.println(thePublicFinalString.get(fieldGetTest));
  9. }
  10. }

输出:

  1. newPublicFinalString

我们接着来看static final字段的情况:

  1. public class FieldGetTest {
  2. public static final String A_PUBLIC_STATIC_FINAL_STRING = "aPublicStaticString";
  3. }
  1. public class FieldTest {
  2. public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
  3. Class<FieldGetTest> fieldGetTestClass = FieldGetTest.class;
  4. Field thePublicFinalString = fieldGetTestClass.getDeclaredField("A_PUBLIC_STATIC_FINAL_STRING");
  5. thePublicFinalString.setAccessible(true);
  6. FieldGetTest fieldGetTest = new FieldGetTest();
  7. thePublicFinalString.set(fieldGetTest,"newPublicStaticFinalString");
  8. System.out.println(thePublicFinalString.get(fieldGetTest));
  9. }
  10. }

这里我直接加上了setAccessible(true)方法的调用,结果:

  1. Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field person.andy.concurrency.reflect.field.FieldGetTest.A_PUBLIC_STATIC_FINAL_STRING to java.lang.String
  2. at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
  3. at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
  4. at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
  5. at java.lang.reflect.Field.set(Field.java:764)
  6. at person.andy.concurrency.reflect.con.FieldTest.main(FieldTest.java:13)

即使忽略访问控制权限校验,我们也不能设置static final字段的值。

小结

这篇文章,我们主要分析了Field的get和set方法,访问控制权限的校验对这两个方法都起作用,set方法在fianl字段的调用上面还有额外的要求,当使用反射来获取或者设置字段值时,要注意这些情况。