Как прочитать значение частного поля из другого класса в Java?

у меня плохо спроектированный класс в 3rd-party JAR и мне нужно получить доступ к одной из его частная поля. Например, почему мне нужно выбрать private field, это необходимо?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

как я могу использовать отражение, чтобы получить значение stuffIWant?

10 ответов


чтобы получить доступ к частным полям, вам нужно получить их из класса объявил поля, а затем сделать их доступными:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

редактировать: как было прокомментировано aperkins, оба доступа к полю, установка его как доступного и получение значения будет все бросить Exceptions, хотя единственный проверил исключения, о которых вы должны помнить, прокомментированы выше.

на NoSuchFieldException будет быть брошенным, если вы запросили поле по имени, которое не соответствует объявленному полю.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

на IllegalAccessException будет выброшен, если поле не было доступно (например, если оно является частным и не было доступно через пропущенные f.setAccessible(true) линии.

на RuntimeExceptions, которые могут быть брошены, являются либо SecurityExceptions (Если JVM SecurityManager не позволит вам изменить доступность поля), или IllegalArgumentExceptions, Если вы попытаетесь получить доступ к полю на объекте не типа класса поля:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type

попробовать FieldUtils из Apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

отражение-это не единственный способ решить вашу проблему (которая заключается в доступе к частной функциональности/поведению класса/компонента)

альтернативным решением является извлечение класса из.jar, декомпилируйте его, используя (скажем) Джоуд или Jad, измените поле (или добавьте метод доступа) и перекомпилируйте его против оригинала .сосуд. Тогда поставь новую .класс впереди .jar в classpath, или вставьте его в .jar. (утилита jar позволяет извлечь и повторно вставить в существующий .jar)

как отмечается ниже, это решает более широкий вопрос доступа / изменения частного государства, а не просто доступа/изменения поля.

для этого требуется .jar не подписывается, конечно.


еще один вариант, который еще не упоминался: используйте в Groovy. Groovy позволяет получить доступ к переменным частного экземпляра в качестве побочного эффекта дизайна языка. Ли у вас есть геттер для поля, вы можете просто использовать

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

С помощью отражение в Java вы можете получить доступ ко всем private/public поля и методы одного класса к другому .Но согласно Oracle документация в разделе недостатки рекомендовано :

" поскольку отражение позволяет коду выполнять операции, которые были бы незаконными в неотражающем коде, такие как доступ к частным полям и методам, использование отражения может привести к неожиданным побочным эффектам, что может сделать код дисфункциональным и может разрушить переносимость. Отражающий код нарушает абстракции и, следовательно, может изменить поведение с обновлением платформы"

вот следующий код snapts, чтобы продемонстрировать основные понятия отражение

отражение 1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

отражение 2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

надеюсь, это поможет.


как упоминает oxbow_lakes, вы можете использовать отражение, чтобы обойти ограничения доступа (при условии, что ваш SecurityManager позволит вам).

тем не менее, если этот класс настолько плохо спроектирован, что заставляет вас прибегать к такому хакерству, возможно, вам следует искать альтернативу. Конечно, этот маленький хак может сэкономить вам несколько часов, но сколько это будет стоить вам по дороге?


используйте soot Java Optimization framework для непосредственного изменения байт-кода. http://www.sable.mcgill.ca/soot/

Soot полностью написан на Java и работает с новыми версиями Java.


вам нужно сделать следующее:

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}

просто дополнительная заметка об отражении: я заметил в некоторых особых случаях, когда несколько классов с одинаковым именем существуют в разных пакетах, что отражение, используемое в верхнем ответе, может не выбрать правильный класс из объекта. Итак, если вы знаете, что такое пакет.класс объекта, то лучше получить доступ к его частным значениям поля следующим образом:

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(Это пример класса, который не работал для меня)


при использовании Spring,ReflectionTestUtils предоставляет некоторые удобные инструменты, которые помогают здесь с минимальными усилиями. Это описывается как"для использования в сценариях модульного и интеграционного тестирования". Существует также аналогичный класс с именем ReflectionUtils но это описывается как "предназначен только для внутреннего использования" - см. ответ для интерпретации того, что это значит.

для обращения к размещенному пример:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");