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


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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

10 397

10 ответов:

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

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

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

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

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

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

The 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 не подписываться, конечно.

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

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

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

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

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

Reflection1.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");
    }

}

Reflection2.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/

сажа полностью написана на 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");