题 Java反射 - 访问受保护的字段


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


31
2018-04-09 17:45


起源


如果你陈述你所尝试的(确切地)和发生的事情(确切地说),问题会更好。 - Tom Hawtin - tackline


答案:


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

即使这些问题,这样的事情也会起作用:

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);
  }
}

51
2018-04-09 18:08



如果有安全管理器,则会失败。您需要在PriviledgedAction中结束对setAccessible和getDeclaredField的调用,并通过java.security.AccessController.doPrivileged(...)运行它 - Varkhan
我全心全意地爱你 - Or Gal
这在Android上不起作用:(我的类树看起来像这样A-> B-> C并且CI内部无法获得在A中声明的受保护字段的值。注意所有三个类都在不同的包中。任何想法如何获取在这附近? - Moonwalker


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

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));
    }
}

6
2018-04-08 01:12





field = myclass.getDeclaredField("myname");
field.setAccessible(true);
field.set(myinstance, newvalue);

4
2018-04-09 17:55



如果存在阻止相关权限的安全管理器,则这可能不起作用。 - Miguel Ping
getField(String name)仅获取公共字段。 - Moro
@Moro:谢谢,我更正了代码 - Maurice Perry
尝试使用Thanx,但是作为Javadoc(我试过)它返回一个Field对象,它反映了由此Class对象表示的类或接口的指定声明字段。它没有通过超级课程。 - Moro
对不起,您不清楚声明字段属于哪个类 - Maurice Perry


使用 FieldUtils.writeField(object, "fieldname", value, true) 要么 readField(object, "fieldname", true) 从 Apache Commons lang3


4
2017-09-22 10:53





我不想拖进更多的库,所以我做了一个适合我的纯粹库。它是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);
        }
    }

}

1
2017-12-12 06:25





如果你刚刚获得受保护的领域

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

如果您正在使用Eclipse 按Ctrl + 空间 键入“。”时将显示方法列表。在对象之后


1
2017-09-16 15:52





如果使用Spring, ReflectionTestUtils 提供一些方便的工具,以最小的努力帮助这里。

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

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

或者设置此字段的值:

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

1
2018-01-18 12:56





你是否可能从一个不同的对象意味着一个不受信任的上下文 SecurityManager 组?这会打破类型系统,所以你不能。从可信上下文中,您可以致电 setAccessible 击败类型系统。理想情况下,不要使用反射。


0
2018-04-09 17:57



“理想情况下,不要使用反射。”为什么? OP特别试图使用反射......虽然他没有说明原因,但反射有许多合法用途(特别是在测试代码中)。 - David Citron
虽然有合理的反射用途,但大部分都没有。特别是如果您正在徘徊于尊重Java(1.0)语言访问规则的传统能力,您可能不需要它。 - Tom Hawtin - tackline
@Tom ...除非你试图编写特定的JUnit测试用例,而不是简单地为测试用例放松访问规则 - David Citron
单元测试“很有趣”。您可能会争辩说它会强制您的代码更清晰(或者不必要的一般)。测试不一定遵循良好代码的通常规则。 - Tom Hawtin - tackline
是汤姆,这是测试用例问题 - Moro


你可以做点什么......

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的回答中所述。


0
2017-10-18 22:37



返回Field对象的数组,这些对象反映由此Class对象表示的类或接口声明的所有字段。它没有通过超级课程 - Moro
你能更具体地解决这个问题吗?你说它只获得子类中的字段?正如Maurice所建议的那样,您是否确保传递了Class.forName()方法的超类名称,并且可访问性已被修改? - Craig Otis


用于在此超类或任何超类中运行任何getter的通用实用程序方法。

改编自 马吕斯的 回答。

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;
}

希望这对某人有用。


0