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

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

Ответ 1

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

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

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

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

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

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

RuntimeException, который может быть сброшен, либо SecurityException (если JVM SecurityManager не позволит вам изменять доступность поля) или IllegalArgumentException s, если вы попытаетесь получить доступ к полю объекта а не типа класса поля:

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

Ответ 2

Попробуйте FieldUtils от apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

Ответ 3

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

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

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

Это требует, чтобы .jar не был подписан, конечно.

Ответ 4

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

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

Ответ 5

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

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

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

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

}

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

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

Чтобы обратиться к опубликованному примеру:

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

Ответ 11

Вы можете использовать @JailBreak от Manifold для прямого, типобезопасного отражения Java:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreak разблокирует локальную переменную foo в компиляторе для прямого доступа ко всем членам в иерархии Foo.

Точно так же вы можете использовать метод расширения jailbreak() для одноразового использования:

foo.jailbreak().stuffIWant = "123";

С помощью метода jailbreak() вы можете получить доступ к любому члену в иерархии Foo.

В обоих случаях компилятор разрешает доступ к полю для вас безопасно, как если бы это было открытое поле, тогда как Manifold генерирует эффективный код отражения для вас изнутри.

Узнайте больше о коллектор.