【问题标题】:Java reflection - access protected fieldJava 反射 - 访问受保护的字段
【发布时间】:2010-10-18 15:01:21
【问题描述】:

如何通过反射从对象访问继承的受保护字段?

【问题讨论】:

  • 如果你说出你尝试了什么(准确地)和发生了什么(准确地),这个问题会更好。

标签: java reflection field protected


【解决方案1】:

使用此实用程序:

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Stream;

import static java.lang.String.format;

public final class ReflectionUtils {

    private ReflectionUtils() { }

    private static final String GETTER_PREFIX = "get";
    private static final String SETTER_PREFIX = "set";

    /**
     * Get name of getter
     *
     * @param fieldName fieldName
     * @return getter name
     */
    public static String getterByFieldName(String fieldName) {
        if (isStringNullOrEmpty(fieldName))
            return null;

        return convertFieldByAddingPrefix(fieldName, GETTER_PREFIX);
    }

    /**
     * Get name of setter
     *
     * @param fieldName fieldName
     * @return setter name
     */
    public static String setterByFieldName(String fieldName) {
        if (isStringNullOrEmpty(fieldName))
            return null;

        return convertFieldByAddingPrefix(fieldName, SETTER_PREFIX);
    }

    /**
     * Get the contents of the field with any access modifier
     *
     * @param obj obj
     * @param fieldName fieldName
     * @return content of field
     */
    public static Object getFieldContent(Object obj, String fieldName) {
        if (!isValidParams(obj, fieldName))
            return null;

        try {
            Field declaredField = getFieldAccessible(obj, fieldName);
            return declaredField.get(obj);
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("Cannot get field content for field name: " + fieldName, e);
        }
    }

    /**
     * @param clazz clazz
     * @param fieldName fieldName
     * @return content static field
     */
    public static Object getStaticFieldContent(final Class<?> clazz, final String fieldName) {
        try {
            Field field = getFieldWithCheck(clazz, fieldName);
            field.setAccessible(true);
            return field.get(clazz);
        } catch (Exception e) {
            String exceptionMsg = format("Cannot find or get static field: '%s' from class: '%s'", fieldName, clazz);
            throw new RuntimeException(exceptionMsg, e);
        }
    }

    /**
     * Set the contents to the field with any access modifier
     *
     * @param obj obj
     * @param fieldName fieldName
     * @param value value
     */
    public static void setFieldContent(Object obj, String fieldName, Object value) {
        if (!isValidParams(obj, fieldName))
            return;

        try {
            Field declaredField = getFieldAccessible(obj, fieldName);
            declaredField.set(obj, value);
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("Cannot set field content for field name: " + fieldName, e);
        }
    }

    /**
     * Call a method with any access modifier
     *
     * @param obj obj
     * @param methodName methodName
     * @return result of method
     */
    public static Object callMethod(Object obj, String methodName) {
        if (!isValidParams(obj, methodName))
            return null;

        try {
            Method method = obj.getClass().getMethod(methodName);
            method.setAccessible(true);
            return method.invoke(obj);
        } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new IllegalArgumentException("Cannot invoke method name: " + methodName, e);
        }
    }

    /**
     * Get all fields even from parent
     *
     * @param clazz clazz
     * @return array of fields
     */
    public static Field[] getAllFields(Class<?> clazz) {
        if (clazz == null) return null;

        List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
        if (clazz.getSuperclass() != null) {
            // danger! Recursion
            fields.addAll(Arrays.asList(getAllFields(clazz.getSuperclass())));
        }
        return fields.toArray(new Field[] {});
    }

    /**
     * Get the Field from Object even from parent
     *
     * @param obj obj
     * @param fieldName fieldName
     * @return {@code Optional}
     */
    public static Optional<Field> getField(Object obj, String fieldName) {
        if (!isValidParams(obj, fieldName))
            return Optional.empty();

        Class<?> clazz = obj.getClass();
        return getField(clazz, fieldName);
    }

    /**
     * Get the Field from Class even from parent
     *
     * @param clazz clazz
     * @param fieldName fieldName
     * @return {@code Optional}
     */
    public static Optional<Field> getField(Class<?> clazz, String fieldName) {
        if (!isValidParams(clazz, fieldName))
            return Optional.empty();

        Field[] fields = getAllFields(clazz);
        return Stream.of(fields)
                .filter(x -> x.getName().equals(fieldName))
                .findFirst();
    }

    /**
     * @param clazz clazz
     * @param fieldName fieldName
     * @return Class
     */
    public static Class<?> getFieldType(Class<?> clazz, String fieldName) {
        return getFieldWithCheck(clazz, fieldName).getType();
    }

    /**
     * @param clazz clazz
     * @param fieldName fieldName
     * @return Field
     */
    public static Field getFieldWithCheck(Class<?> clazz, String fieldName) {
        return ReflectionUtils.getField(clazz, fieldName)
                .orElseThrow(() -> {
                    String msg = String.format("Cannot find field name: '%s' from class: '%s'", fieldName, clazz);
                    return new IllegalArgumentException(msg);
                });
    }

    /**
     * Get the field values with the types already listed according to the field type
     *
     * @param clazz clazz
     * @param fieldName fieldName
     * @param fieldValue fieldValue
     * @return value cast to specific field type
     */
    public static Object castFieldValueByClass(Class<?> clazz, String fieldName, Object fieldValue) {
        Field field = getField(clazz, fieldName)
                .orElseThrow(() -> new IllegalArgumentException(String.format("Cannot find field by name: '%s'", fieldName)));

        Class<?> fieldType = field.getType();

        return castFieldValueByType(fieldType, fieldValue);
    }

    /**
     * @param fieldType fieldType
     * @param fieldValue fieldValue
     * @return casted value
     */
    public static Object castFieldValueByType(Class<?> fieldType, Object fieldValue) {
        if (fieldType.isAssignableFrom(Boolean.class)) {
            if (fieldValue instanceof String) {
                return convertStringToBoolean((String) fieldValue);
            }
            if (fieldValue instanceof Number) {
                return !(fieldValue).equals(0);
            }
            return fieldValue;
        }

        else if (fieldType.isAssignableFrom(Double.class)) {
            if (fieldValue instanceof String) {
                return Double.valueOf((String)fieldValue);
            }
            return ((Number) fieldValue).doubleValue();
        }

        else if (fieldType.isAssignableFrom(Long.class)) {
            if (fieldValue instanceof String) {
                return Long.valueOf((String)fieldValue);
            }
            return ((Number) fieldValue).longValue();
        }

        else if (fieldType.isAssignableFrom(Float.class)) {
            if (fieldValue instanceof String) {
                return Float.valueOf((String)fieldValue);
            }
            return ((Number) fieldValue).floatValue();
        }

        else if (fieldType.isAssignableFrom(Integer.class)) {
            if (fieldValue instanceof String) {
                return Integer.valueOf((String)fieldValue);
            }
            return ((Number) fieldValue).intValue();
        }

        else if (fieldType.isAssignableFrom(Short.class)) {
            if (fieldValue instanceof String) {
                return Short.valueOf((String)fieldValue);
            }
            return ((Number) fieldValue).shortValue();
        }

        return fieldValue;
    }

    private static boolean convertStringToBoolean(String s) {
        String trim = s.trim();
        return !trim.equals("") && !trim.equals("0") && !trim.toLowerCase().equals("false");
    }

    private static boolean isValidParams(Object obj, String param) {
        return (obj != null && !isStringNullOrEmpty(param));
    }

    private static boolean isStringNullOrEmpty(String fieldName) {
        return fieldName == null || fieldName.trim().length() == 0;
    }

    private static Field getFieldAccessible(Object obj, String fieldName) {
        Optional<Field> optionalField = getField(obj, fieldName);
        return optionalField
                .map(el -> {
                    el.setAccessible(true);
                    return el;
                })
                .orElseThrow(() -> new IllegalArgumentException("Cannot find field name: " + fieldName));
    }

    private static String convertFieldByAddingPrefix(String fieldName, String prefix) {
        return prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }
}

【讨论】:

    【解决方案2】:

    如果使用 Spring,ReflectionTestUtils 提供了一些方便的工具,可以帮助您轻松解决问题。

    例如,要获取已知为int 的受保护字段值:

    int theIntValue = (int)ReflectionTestUtils.getField(theClass, "theProtectedIntField");
    

    或者设置该字段的值:

    ReflectionTestUtils.setField(theClass, "theProtectedIntField", theIntValue);
    

    【讨论】:

      【解决方案3】:

      使用来自Apache Commons lang3FieldUtils.writeField(object, "fieldname", value, true)readField(object, "fieldname", true)

      【讨论】:

        【解决方案4】:

        如果您只是获得受保护的字段

        Field protectedfield = Myclazz.class.getSuperclass().getDeclaredField("num");
        

        如果您使用的是 Eclipse,Ctrl + Space 将在您键入“.”时显示方法列表。在对象之后

        【讨论】:

          【解决方案5】:

          您可能遇到的两个问题 - 该字段可能无法正常访问(私有),并且它不在您正在查看的类中,而是在层次结构的某个位置。

          即使遇到这些问题,这样的方法也可以:

          public class SomeExample {
          
            public static void main(String[] args) throws Exception{
              Object myObj = new SomeDerivedClass(1234);
              Class myClass = myObj.getClass();
              Field myField = getField(myClass, "value");
              myField.setAccessible(true); //required if field is not normally accessible
              System.out.println("value: " + myField.get(myObj));
            }
          
            private static Field getField(Class clazz, String fieldName)
                  throws NoSuchFieldException {
              try {
                return clazz.getDeclaredField(fieldName);
              } catch (NoSuchFieldException e) {
                Class superClass = clazz.getSuperclass();
                if (superClass == null) {
                  throw e;
                } else {
                  return getField(superClass, fieldName);
                }
              }
            }
          }
          
          class SomeBaseClass {
            private Integer value;
          
            SomeBaseClass(Integer value) {
              this.value = value;
            }
          }
          
          class SomeDerivedClass extends SomeBaseClass {
            SomeDerivedClass(Integer value) {
              super(value);
            }
          }
          

          【讨论】:

          • 如果有安全管理器,这将失败。您需要将对 setAccessible 和 getDeclaredField 的调用封装在一个 PrivilegedAction 中,并通过 java.security.AccessController.doPrivileged(...) 运行它
          • 我全心全意地爱你
          • 这在 Android 上不起作用 :( 我的类树看起来像这样 A->B->C 并且在 CI 内部无法获取在 A 中声明的受保护字段的值。注意所有三个类都在不同的包。任何想法如何解决这个问题?
          【解决方案6】:

          一种通用的实用方法,用于在此或任何超类中运行任何 getter。

          改编自Marius's答案。

          public static Object RunGetter(String fieldname, Object o){
              Object result = null;
              boolean found = false;
              //Search this and all superclasses:
              for (Class<?> clas = o.getClass(); clas != null; clas = clas.getSuperclass()){
                  if(found){
                     break;
                  }
                  //Find the correct method:
                  for (Method method : clas.getDeclaredMethods()){
                      if(found){
                          break;
                      }
                      //Method found:
                      if ((method.getName().startsWith("get")) && (method.getName().length() == (fieldname.length() + 3))){ 
                          if (method.getName().toLowerCase().endsWith(fieldname.toLowerCase())){                            
                              try{
                                  result = method.invoke(o);  //Invoke Getter:
                                  found = true;
                              } catch (IllegalAccessException | InvocationTargetException ex){
                                  Logger.getLogger("").log(Level.SEVERE, "Could not determine method: " + method.getName(), ex);
                              }
                          }
                      }
                  }
              }
              return result;
          }
          

          希望这对某人有用。

          【讨论】:

            【解决方案7】:

            我不想拖入更多库,所以我制作了一个适合我的纯库。它是 jweyrich 方法之一的扩展:

            import java.lang.reflect.Field;
            import java.lang.reflect.Modifier;
            import java.util.Date;
            import java.util.Random;
            import java.util.UUID;
            
            public abstract class POJOFiller {
            
                static final Random random = new Random();
            
                public static void fillObject(Object ob) {
                    Class<? extends Object> clazz = ob.getClass();
            
                    do {
                        Field[] fields = clazz.getDeclaredFields();
                        fillForFields(ob, fields);
            
                        if (clazz.getSuperclass() == null) {
                            return;
                        }
                        clazz = clazz.getSuperclass();
            
                    } while (true);
            
                }
            
                private static void fillForFields(Object ob, Field[] fields) {
                    for (Field field : fields) {
                        field.setAccessible(true);
            
                        if(Modifier.isFinal(field.getModifiers())) {
                            continue;
                        }
            
                        try {
                            field.set(ob, generateRandomValue(field.getType()));
                        } catch (IllegalArgumentException | IllegalAccessException e) {
                            throw new IllegalStateException(e);
                        }
                    }
                }
            
                static Object generateRandomValue(Class<?> fieldType) {
                    if (fieldType.equals(String.class)) {
                        return UUID.randomUUID().toString();
                    } else if (Date.class.isAssignableFrom(fieldType)) {
                        return new Date(System.currentTimeMillis());
                    } else if (Number.class.isAssignableFrom(fieldType)) {
                        return random.nextInt(Byte.MAX_VALUE) + 1;
                    } else if (fieldType.equals(Integer.TYPE)) {
                        return random.nextInt();
                    } else if (fieldType.equals(Long.TYPE)) {
                        return random.nextInt();
                    } else if (Enum.class.isAssignableFrom(fieldType)) {
                        Object[] enumValues = fieldType.getEnumConstants();
                        return enumValues[random.nextInt(enumValues.length)];
                    } else if(fieldType.equals(Integer[].class)) {
                        return new Integer[] {random.nextInt(), random.nextInt()};
                    }
                    else {
                        throw new IllegalArgumentException("Cannot generate for " + fieldType);
                    }
                }
            
            }
            

            【讨论】:

              【解决方案8】:

              使用反射来访问类实例的成员,使它们可访问并设置它们各自的值。当然,您必须知道要更改的每个成员的名称,但我想这不会有问题。

              public class ReflectionUtil {
                  public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
                      try {
                          return clazz.getDeclaredField(fieldName);
                      } catch (NoSuchFieldException e) {
                          Class superClass = clazz.getSuperclass();
                          if (superClass == null) {
                              throw e;
                          } else {
                              return getField(superClass, fieldName);
                          }
                      }
                  }
                  public static void makeAccessible(Field field) {
                      if (!Modifier.isPublic(field.getModifiers()) ||
                          !Modifier.isPublic(field.getDeclaringClass().getModifiers()))
                      {
                          field.setAccessible(true);
                      }
                  }
              }
              
              public class Application {
                  public static void main(String[] args) throws Exception {
                      KalaGameState obj = new KalaGameState();
                      Field field = ReflectionUtil.getField(obj.getClass(), 'turn');
                      ReflectionUtil.makeAccessible(field);
                      field.setInt(obj, 666);
                      System.out.println("turn is " + field.get(obj));
                  }
              }
              

              【讨论】:

                【解决方案9】:

                你可以这样做......

                Class clazz = Class.forName("SuperclassObject");
                
                Field fields[] = clazz.getDeclaredFields();
                
                for (Field field : fields) {
                    if (field.getName().equals("fieldImLookingFor")) {
                        field.set...() // ... should be the type, eg. setDouble(12.34);
                    }
                }
                

                您可能还需要更改可访问性,如 Maurice 的回答中所述。

                【讨论】:

                • 返回一个 Field 对象数组,反映由此 Class 对象表示的类或接口声明的所有字段。它没有通过超类
                • 您能具体说明一下这个问题吗?你是说它只获得了你的子类中的字段?您是否确保 Class.forName() 方法正在传递超类的名称,并且按照 Maurice 的建议修改了可访问性?
                【解决方案10】:
                field = myclass.getDeclaredField("myname");
                field.setAccessible(true);
                field.set(myinstance, newvalue);
                

                【讨论】:

                • 如果有安全管理器阻止相关权限,这可能不起作用。
                • getField(String name) 只获取公共字段。
                • 感谢尝试,但作为 Javadoc(我尝试过),它返回一个 Field 对象,该对象反映了该 Class 对象表示的类或接口的指定声明字段。它不经过超类。
                • 对不起,不清楚你没有 new 在哪个类中声明了该字段
                【解决方案11】:

                您可能是指来自不同对象的带有SecurityManager 集的不受信任的上下文?那会破坏类型系统,所以你不能。在受信任的上下文中,您可以调用 setAccessible 来击败类型系统。理想情况下,不要使用反射。

                【讨论】:

                • “理想情况下,不要使用反射。”为什么? OP 专门尝试使用反射......虽然他没有说明原因,但反射有许多合法用途(尤其是在测试代码中)。
                • 虽然反射有合法的用途,但大多数不是。特别是,如果您正在考虑尊重 Java (1.0) 语言访问规则的遗留能力,您可能不需要它。
                • @Tom ...除非您尝试编写特定的 JUnit 测试用例而不放松测试用例的访问规则
                • 单元测试很“有趣”。您可能会争辩说它迫使您的代码更清洁(或者不必要地通用)。测试不一定遵循好代码的常规规则。
                猜你喜欢
                • 2016-07-19
                • 2020-11-02
                • 1970-01-01
                • 2012-01-22
                • 2011-06-07
                • 2012-05-26
                • 1970-01-01
                • 1970-01-01
                • 2015-05-09
                相关资源
                最近更新 更多