В чем разница между getFields и getDeclaredFields в отражении Java

195

Меня немного смущает различие между getFieldsметодом и getDeclaredFieldsметодом при использовании отражения Java.

Я прочитал, что getDeclaredFieldsдает вам доступ ко всем полям класса и getFieldsвозвращает только открытые поля. Если это так, почему бы вам просто не использовать всегда getDeclaredFields?

Может ли кто-нибудь подробно рассказать об этом и объяснить разницу между этими двумя методами, а также то, когда и почему вы хотите использовать один метод над другим?

BlackHatSamurai
источник
3
getFieldможет получить поле, унаследованное от суперкласса, но getDeclaredFieldне может. getDeclaredFieldограничьте себя классом, для которого вы вызываете функцию.
user2336315
@ user2336315 это правильно, но getFieldне может получить доступ к частным пользователям
Madbreaks

Ответы:

258

GetFields ()

Все public поля по всей иерархии классов.

getDeclaredFields ()

Все поля, независимо от их доступности, но только для текущего класса, а не базовые классы, от которых текущий класс может наследовать.

Чтобы получить все поля в иерархии, я написал следующую функцию:

public static Iterable<Field> getFieldsUpTo(@Nonnull Class<?> startClass, 
                                   @Nullable Class<?> exclusiveParent) {

   List<Field> currentClassFields = Lists.newArrayList(startClass.getDeclaredFields());
   Class<?> parentClass = startClass.getSuperclass();

   if (parentClass != null && 
          (exclusiveParent == null || !(parentClass.equals(exclusiveParent)))) {
     List<Field> parentClassFields = 
         (List<Field>) getFieldsUpTo(parentClass, exclusiveParent);
     currentClassFields.addAll(parentClassFields);
   }

   return currentClassFields;
}

exclusiveParentКласс предусмотрен для предотвращения извлечения полей из Object. Это может быть, nullесли вы хотите Objectполя.

Чтобы уточнить, Lists.newArrayListприходит из Гуавы.

Обновить

К вашему сведению, приведенный выше код опубликован на GitHub в моем проекте LibEx в ReflectionUtils .

Джон Б
источник
8
Отличный ответ, но следует отметить, что частные поля в суперклассах не могут использоваться экземплярами текущего класса для Field#getи аналогичными методами. Другими словами, этот подход не разрешает текущему классу доступ к закрытому интерфейсу его суперкласса, как это делает типичная компиляция.
FThompson
4
@Vulcan Истина, если только код не написан для использования отражения с целью изменения области действия с помощью setAccessibleи нет Менеджера безопасности на месте
Джон Б
Небольшая гниль, должна быть "(независимо от доступности)" не "(независимо от объема)". Все поля имеют одинаковую область видимости, а именно тело класса .
ишавит
@yshavit Спасибо. Обновлено.
Джон Б
1
Это не будет. Поскольку к privateполям можно получить доступ только через getDeclaredFieldsкоторый зависит от класса. Каждое поле (даже с одинаковым типом и именем) будет отдельным Fieldэкземпляром.
Джон Б
7

Как уже упоминалось, Class.getDeclaredField(String)смотрит только на те поля, из Classкоторых вы его называете.

Если вы хотите найти Fieldв Classиерархии, вы можете использовать эту простую функцию:

/**
 * Returns the first {@link Field} in the hierarchy for the specified name
 */
public static Field getField(Class<?> clazz, String name) {
    Field field = null;
    while (clazz != null && field == null) {
        try {
            field = clazz.getDeclaredField(name);
        } catch (Exception e) {
        }
        clazz = clazz.getSuperclass();
    }
    return field;
}

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

/**
 * Sets {@code value} to the first {@link Field} in the {@code object} hierarchy, for the specified name
 */
public static void setField(Object object, String fieldName, Object value) throws Exception {
    Field field = getField(object.getClass(), fieldName);
    field.setAccessible(true);
    field.set(object, value);
}
IvanRF
источник
небольшая модификация, чтобы по-прежнему try try { field = clazz.getDeclaredField(name); } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); if(clazz==null){ throw e; } }
выдавать
5

public Field[] getFields() throws SecurityException

Возвращает массив, содержащий объекты Field, отражающие все доступные публичные поля класса или интерфейса, представленные этим объектом Class. Элементы в возвращаемом массиве не сортируются и не располагаются в каком-либо определенном порядке. Этот метод возвращает массив длины 0, если у класса или интерфейса нет доступных открытых полей или если он представляет класс массива, примитивный тип или void.

В частности, если этот объект Class представляет класс, этот метод возвращает открытые поля этого класса и всех его суперклассов. Если этот объект Class представляет интерфейс, этот метод возвращает поля этого интерфейса и всех его суперинтерфейсов.

Поле неявной длины для класса массива не отражается этим методом. Пользовательский код должен использовать методы класса Array для манипулирования массивами.


public Field[] getDeclaredFields() throws SecurityException

Возвращает массив объектов Field, отражающих все поля, объявленные классом или интерфейсом, представленным этим объектом Class. Это включает в себя открытые, защищенные, доступ по умолчанию (пакет) и частные поля, но исключает унаследованные поля. Элементы в возвращаемом массиве не сортируются и не располагаются в каком-либо определенном порядке. Этот метод возвращает массив длины 0, если класс или интерфейс не объявляет полей, или если этот объект Class представляет примитивный тип, класс массива или void.


А что если мне понадобятся все поля из всех родительских классов? Необходим некоторый код, например, с https://stackoverflow.com/a/35103361/755804 :

public static List<Field> getAllModelFields(Class aClass) {
    List<Field> fields = new ArrayList<>();
    do {
        Collections.addAll(fields, aClass.getDeclaredFields());
        aClass = aClass.getSuperclass();
    } while (aClass != null);
    return fields;
}
18446744073709551615
источник