Java 反射 - 访问受保护的字段
Posted
技术标签:
【中文标题】Java 反射 - 访问受保护的字段【英文标题】:Java reflection - access protected field 【发布时间】:2010-10-18 15:01:21 【问题描述】:如何通过反射从对象访问继承的受保护字段?
【问题讨论】:
如果你说出你尝试了什么(准确地)和发生了什么(准确地),这个问题会更好。 【参考方案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 lang3 的FieldUtils.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 测试用例而不放松测试用例的访问规则 单元测试很“有趣”。您可能会争辩说它迫使您的代码更清洁(或者不必要地通用)。测试不一定遵循好代码的常规规则。以上是关于Java 反射 - 访问受保护的字段的主要内容,如果未能解决你的问题,请参考以下文章