Как получить доступ к приватным полям через рефлексию в Java
Что проверяют
Знаете ли вы базовый API рефлексии и, главное, его цену на Android: обфускацию, ограничения на скрытые API платформы и потерю инкапсуляции. Следом часто спрашивают, где рефлексия уместна и чем её заменить в тестах.
Как отвечать
Базовый путь занимает три строки:
Field field = Main.class.getDeclaredField("secret");
field.setAccessible(true);
String value = (String) field.get(obj);getDeclaredField: находит поле с любым модификатором доступа, но только объявленное в этом классе. Поле родителя ищут черезgetSuperclass();getField: толькоpublic, зато с учётом унаследованных;setAccessible(true): отключает проверку доступа. Для статического поля объект не нужен:field.get(null);- исключения:
NoSuchFieldExceptionпри неверном имени иIllegalAccessException, если доступ не открыт.
В Kotlin то же самое: javaClass.getDeclaredField("secret").apply { isAccessible = true }. Через kotlin-reflect доступны memberProperties, но библиотека заметно утяжеляет APK.
Дальше Android-специфика, ради которой вопрос и задают:
- R8 и ProGuard: в релизной сборке поле переименовано в
a, и поиск по строке упадёт. Нужно правило-keepclassmembersдля этого поля; - скрытые API: с Android 9 рефлексия к непубличным полям и методам фреймворка ограничена. Часть доступна с предупреждением в logcat, часть заблокирована, и
getDeclaredFieldбросаетNoSuchFieldException. К вашим собственным классам это не относится; - производительность: поиск поля медленный, объект
Fieldстоит закешировать, а не искать в цикле.
Где спотыкаются
Проверяют только debug-сборку. Там всё работает, а в релизе после обфускации тот же код падает у пользователей.
Используют рефлексию в тестах вместо нормального решения. Проще сделать поле internal или пакетным и пометить @VisibleForTesting, тогда тест не сломается от переименования.
Пытаются подменить static final константу и удивляются, что ничего не изменилось. Константы примитивов и строк компилятор встраивает в места использования, и новое значение до них не доходит.