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

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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


person Frank Krueger    schedule 28.07.2009    source источник


Ответы (14)


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

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 (если SecurityManager JVM не позволяет вам изменять доступность поля), либо IllegalArgumentExceptions, если вы пытаетесь получить доступ к полю на объекте, не относящемся к типу класса поля:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
person oxbow_lakes    schedule 28.07.2009
comment
Также обратите внимание, что вам нужно будет поймать одно или два исключения из f.get - person aperkins; 28.07.2009
comment
не могли бы вы объяснить комментарии об исключениях? код будет работать, но выдаст исключения? или код может выдавать исключения? - person Nir Levy; 29.07.2009
comment
@Nir - Нет - по всей вероятности, код будет работать нормально (поскольку SecurityManager по умолчанию позволяет изменять доступность полей), но вы должны обрабатывать отмеченные исключения (либо поймать их, либо объявить их быть переброшенным). Я немного изменил свой ответ. Возможно, вам будет полезно написать небольшой тестовый пример, чтобы поиграть и посмотреть, что произойдет. - person oxbow_lakes; 29.07.2009
comment
@Nir - именно то, что сказал стариц. Код будет проверять исключения вокруг получения объявленного поля и получения значения из объекта. Вам просто нужно позаботиться об этих случаях. - person aperkins; 29.07.2009
comment
очень полезный ответ. Я случайно отправил статический объект Class ‹?› Вместо экземпляра. Хорошо работал для статических полей, но меня не смутило остальное :-) - person Karl Kildén; 02.10.2012
comment
a.setAccessible(true); String var = a.getName(); a.get(var) дает мне Can not set final to java.lang.String - person Ava; 08.12.2014
comment
Извините, этот ответ меня сбивает. Возможно, покажите пример типовой обработки исключений. Похоже, что исключения будут возникать, когда классы неправильно связаны друг с другом. В примере кода создается впечатление, что исключения будут выброшены на соответствующие значения. - person LaFayette; 29.02.2016
comment
getDeclaredField() не находит поля, если они определены в родительских классах - вам также необходимо выполнить итерацию по иерархии родительских классов, вызывая getDeclaredField() для каждого, пока вы не найдете совпадение (после чего вы можете вызвать setAccessible(true)) или не достигнете Object . - person Luke Hutchison; 13.02.2017
comment
@oxbow_lakes не нарушает исходную семантику частной переменной. Тогда какова цель частного, если мы еще можем изменить его с помощью рефлексии. - person legend; 16.09.2018
comment
@legend вы можете установить менеджер безопасности, чтобы запретить такой доступ. Начиная с Java 9, такой доступ не должен работать через границы модуля (если модуль не открыт явно), хотя есть переходная фаза, касающаяся применения новых правил. Кроме того, требующие дополнительных усилий, такие как Reflection, всегда имели значение для полей, отличных от private. Это исключает случайный доступ. - person Holger; 25.09.2018
comment
В JDK 9+ / JPMS, если закрытое поле находится в другом модуле (x) из модуля класса, выполняющего код отражения (y), тогда x должен сам открыться (opens) в y для f.setAccessible(true) для успешного выполнения. - person Luke Hutchison; 21.10.2018
comment
Извините, почему за это проголосовали, ответ ниже является наиболее эффективным и простым ответом здесь, используйте FieldUtils! - person Brain Bytes; 27.05.2019
comment
Это был список методов вместо полей, когда я пытался сделать это для bean-компонента, введенного SpringContext и объявленного как интерфейс. Вместо этого работал этот другой ответ: stackoverflow.com/a/46890521/9768291 - person payne; 10.12.2020

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

FieldUtils.readField(object, fieldName, true);
person yegor256    schedule 25.04.2014
comment
Я убежден, что вы можете решить большинство мировых проблем, объединив несколько методов из commons-lang3. - person Cameron; 05.08.2015
comment
@yegor, почему Java позволяет это? Имеются в виду, почему Java разрешает доступ к частным членам? - person Asif Mushtaq; 24.04.2016
comment
@ yegor256 Я все еще могу получить доступ к закрытым членам C # и C ++ .. !! Потом? все создатели языков слабые? - person Asif Mushtaq; 28.04.2016
comment
@UnKnown java - нет. Есть две разные вещи - язык Java и Java как виртуальная машина Java. Последний работает с байт-кодом. И есть библиотеки для управления этим. Таким образом, язык java не позволяет использовать частные поля вне области видимости или не позволяет изменять окончательные ссылки. Но это можно сделать с помощью инструмента. Которая просто находится внутри стандартной библиотеки. - person evgenii; 07.08.2016
comment
@EvgeniiMorozov снова вопрос ушел с почему. Почему Java разрешила использовать или создала эти инструменты? - person Asif Mushtaq; 07.08.2016
comment
@UnKnown, потому что подавляющее большинство сообщества Java понятия не имеют, что такое хорошие методы программирования. Они просто преобразованные программисты C / C ++, которым платят за создание программного обеспечения. Все, что работает, - это их философия. Вот почему Java разрешает такой доступ. - person yegor256; 07.08.2016
comment
@ yegor256, вы имеете в виду, что мы не можем получить доступ к закрытым полям C #? stackoverflow.com/questions/95910/ и я не думаю, что даже глупый программист напишет код для доступа к приватным полям (без всякой причины). - person Asif Mushtaq; 07.08.2016
comment
@UnKnown, почему бы вам не спросить, почему существуют дизассемблеры, позволяющие взломать любую часть любого программного обеспечения? Java на самом деле ничего не позволяет. Просто невозможно остановить людей. Если бы Sun не написал такую ​​библиотеку для работы с байт-кодом, это сделал бы кто-нибудь другой. Это просто вопрос времени, немного любопытства, немного интеллекта - большинство тех вещей, которые означают, что нужно быть настоящим хакером (так сказать, по старинке). - person evgenii; 08.08.2016
comment
@ yegor256, я почти уверен, что ты знаешь, что есть несколько определений хакера. Наличие очень своеобразных инструментов полностью ортогонально хорошему дизайну, что, кстати, субъективно и зависит от ... - person evgenii; 08.08.2016
comment
@evgenii Я не говорю об инструментах сторонних разработчиков. Почему Java создает API отражения для доступа к закрытым членам? Я не понимаю, почему вы не пытаетесь доказать, что все создатели языка слабы, вместо того, чтобы дать настоящий ответ. (Снова и снова я не говорю об инструментах аутсорсинга) - person Asif Mushtaq; 30.08.2016
comment
@UnKnown, они все люди, мы (люди) делаем ошибки, но я очень сомневаюсь, что именно поэтому они создали отражение в java. Вероятно, это хороший вопрос для Джеймса Гослинга. - person evgenii; 31.08.2016
comment
@UnKnown одна из причин заключается в том, что у Java нет доступа для друзей, чтобы разрешить доступ к полю только с помощью инфраструктуры инфраструктуры, такой как DI, ORM или сериализатор XML / JSON. Таким фреймворкам необходим доступ к полям объекта, чтобы правильно сериализовать или инициализировать внутреннее состояние объекта, но вам все равно может потребоваться надлежащее применение инкапсуляции во время компиляции для вашей бизнес-логики. - person Ivan Gammel; 21.12.2016
comment
Вы можете запретить отражение с помощью политик безопасности Java. - person vdshb; 25.01.2017

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

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

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

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

person Brian Agnew    schedule 28.07.2009
comment
Этот способ будет довольно болезненным для простого поля. - person Valentin Rocher; 29.07.2009
comment
Я не согласен. Это позволяет вам не только получить доступ к своему полю, но и при необходимости изменить класс, если доступа к полю оказывается недостаточно. - person Brian Agnew; 29.07.2009
comment
Тогда вам придется сделать это снова. Что произойдет, если jar получит обновление и вы используете отражение для поля, которое больше не существует? Это точно такая же проблема. Вы просто должны справиться с этим. - person Brian Agnew; 23.11.2012
comment
Я удивлен, насколько за него проголосовали против: а) он выделен как практическая альтернатива; б) он подходит для сценариев, в которых изменения видимости поля недостаточно. - person Brian Agnew; 27.12.2012
comment
@BrianAgnew, хотя вы правы, точный вопрос был в том, как мне прочитать приватное поле в Java? и ваш ответ заключается в том, как удалить частное из поля в существующей банке, я думаю, поэтому вы были занижены. - person Remi Morin; 30.08.2013
comment
Утилита jar позволяет извлечь и повторно вставить в существующий .jar или, что еще проще, просто открыть jar с помощью 7-Zip или переименовать .jar в .zip и открыть с помощью проводника Windows. - person wchargin; 09.11.2013
comment
@RemiMorin - изменить поле (или добавить аксессуар) смотрит прямо мне - person Brian Agnew; 20.02.2014
comment
@BrianAgnew, возможно, это просто семантика, но если мы будем придерживаться вопроса (используя отражение для чтения частного поля), отказ от отражения сразу же станет внутренним противоречием. Но я согласен, что вы предоставляете доступ к полю ... но все же поле больше не является частным, поэтому мы снова не придерживаемся чтения частного поля вопроса. С другой стороны, модификация .jar может не работать в некоторых случаях (подписанный jar), необходимо выполнять каждый раз, когда jar обновляется, требует осторожного манипулирования путем к классам (который вы не можете полностью контролировать, если выполняете в контейнер приложения) и т. д. - person Remi Morin; 25.02.2014
comment
Я думаю, это может быть подчеркнуто, что вы не можете прочитать значение stuffIWant таким образом, поэтому это не отвечает на заданный вопрос. - person eis; 09.09.2015
comment
+1 за альтернативу. Я делал это больше раз, чем могу сосчитать. Хотя вам, возможно, не всегда придется снова вставлять его обратно в банку. В большинстве случаев я просто вставляю модифицированный класс в другой пакет в моем собственном проекте и сохраняю jar как зависимость, чтобы класс мог компилироваться (если у него есть какие-либо зависимости от этого jar-файла - они обычно это делают). Если в вашей среде IDE есть собственный декомпилятор, этот процесс должен занять несколько секунд. - person Cypher; 27.08.2016

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

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
person lucas    schedule 19.01.2010
comment
OP специально попросил Java - person Jochen; 06.08.2015
comment
Сегодня многие проекты Java включают groovy. Достаточно того, что в проекте используется отличный DSL Spring, и у них, например, будет groovy в пути к классам. В этом случае этот ответ полезен, и, хотя он не отвечает напрямую на OP, он будет полезен для многих посетителей. - person Amir Abiri; 23.01.2016

Используя Отражение в 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()));
        }
    }

}

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

person Simmant    schedule 24.09.2014

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

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

person Laurence Gonsalves    schedule 28.07.2009
comment
Мне повезло больше, чем это на самом деле, я просто использую этот код для извлечения некоторых данных, после чего я могу бросить их обратно в корзину. - person Frank Krueger; 28.07.2009
comment
Ну, в таком случае, взломайте. :-) - person Laurence Gonsalves; 28.07.2009
comment
Это не дает ответа на вопрос. Чтобы критиковать или запрашивать разъяснения у автора, оставьте комментарий под его сообщением. - person Mureinik; 20.12.2014
comment
@Mureinik - Он отвечает на вопрос словами, которые можно использовать для отражения. Здесь нет примера или какого-либо более подробного объяснения или того, как, но это ответ. Проголосуйте против, если вам это не нравится. - person ArtOfWarfare; 19.01.2017

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

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

person pcpratts    schedule 19.01.2010

Вам необходимо сделать следующее:

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);
}
person Luke Hutchison    schedule 13.02.2017

При использовании Spring:

В контексте тестирования ReflectionTestUtils предоставляет несколько удобных инструментов, которые могут помочь здесь с минимальными усилиями. Он описан как для использования в сценариях модульного и интеграционного тестирования.

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

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

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
person Steve Chambers    schedule 23.10.2017
comment
Если вы решили использовать класс Utils от Spring, вам действительно следует использовать не тестовый (docs.spring.io/spring-framework/docs/current/javadoc-api/org/), если только конечно, вы на самом деле используете его для модульных тестов. - person Leroy; 08.11.2017

Для этого вы можете использовать jOOR.

class Foo {
    private final String value = "ABC";
}
class Bar {
    private final Foo foo = new Foo();
    public String value() {
        return org.joor.Reflect
            .on(this.foo)
            .field("value")
            .get();
    }
}
class BarTest {
    @Test
    void accessPrivateField() {
        Assertions.assertEquals(new Bar().value(), "ABC");
    }
}
person andreoss    schedule 01.07.2020

Просто дополнительное примечание об отражении: я наблюдал в некоторых особых случаях, когда несколько классов с одинаковым именем существуют в разных пакетах, это отражение, используемое в верхнем ответе, может не выбрать правильный класс из объекта. Итак, если вы знаете, что такое 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);

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

person xtof54    schedule 09.09.2015

Вы можете использовать Manifold @JailBreak для прямого типобезопасного 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 генерирует эффективный код отражения для вас под капотом.

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

person Scott    schedule 09.12.2018

Это довольно просто сделать с помощью инструмента XrayInterface. Просто определите недостающие геттеры / сеттеры, например.

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

и просмотрите свой плохо спроектированный проект:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

Внутренне это зависит от размышлений.

person CoronA    schedule 27.11.2019

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

Просто создайте для этого public setter(Field f, Object value) и public Object getter(Field f). Вы даже можете самостоятельно выполнить некоторую проверку безопасности внутри этих функций-членов. Например. для сеттера:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

Конечно, теперь вам нужно узнать java.lang.reflect.Field для sString перед установкой значения поля.

Я использую эту технику в общем ResultSet-to-and-from-model-mapper.

person karldegen    schedule 25.11.2019