Получить имя поля класса в Java

public class Product{
    private String id;
    private String name;
    private String description;
    // Getters and setters..
}

Из приведенного выше класса я хочу получить имя поля описания во время выполнения:

Query query = new Query();
        if (productSearchCriteria.getDescription().isPresent()) {
            query.addCriteria(Criteria.where("description").is(productSearchCriteria.getDescription().get()));
        }

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

Осматриваясь, я нашел библиотеку reflection-util.

 String numberProperty = PropertyUtils.getPropertyName(Product.class, Product::getDescription);

Это дало мне исключение:

Caused by: java.lang.ClassCastException: class domain.Product$ByteBuddy$QIcFk1aK cannot be cast to class domain.Product (domain.Product$ByteBuddy$QIcFk1aK is in unnamed module of loader net.bytebuddy.dynamic.loading.ByteArrayClassLoader @33943b71; domain.Product is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @5be4b0e6)
    at de.cronn.reflection.util.PropertyUtils.findMethodByGetter(PropertyUtils.java:322) ~[reflection-util-2.6.0.jar:na]
    at de.cronn.reflection.util.PropertyDescriptorCache.lambda$getMethod$4(PropertyDescriptorCache.java:178) ~[reflection-util-2.6.0.jar:na]
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[na:na]
    at de.cronn.reflection.util.PropertyDescriptorCache.getMethod(PropertyDescriptorCache.java:178) ~[reflection-util-2.6.0.jar:na]
    at de.cronn.reflection.util.PropertyUtils.getMethod(PropertyUtils.java:315) ~[reflection-util-2.6.0.jar:na]
    at de.cronn.reflection.util.PropertyUtils.getPropertyDescriptor(PropertyUtils.java:282) ~[reflection-util-2.6.0.jar:na]
    at de.cronn.reflection.util.PropertyUtils.getPropertyName(PropertyUtils.java:290) ~[reflection-util-2.6.0.jar:na]
    at fete.bird.fetebirdproduct.common.querybuilder.QueryBuilder.BuildQuery(QueryBuilder.java:24) ~[main/:na]
    at fete.bird.fetebirdproduct.service.consumer.ProductListener.GetProducts(ProductListener.java:56) ~[main/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at  ... 9 common frames omitted

Я использую JDK 14. Я знаю, что есть API отражения Java. Я не могу жестко закодировать имя поля.

Есть ли решение?


person San Jaisy    schedule 28.08.2020    source источник
comment
Проблема в этот метод, где код использует PropertyUtils.class.getClassLoader() вместо beanClass.getClassLoader(). Но учитывая, что автор этой библиотеки, похоже, не знает о ClassValue, но кэширует вещи в обычных картах в static полях, я могу только отговорить его от использования в среде с динамической (повторной) загрузкой кода.   -  person Holger    schedule 28.08.2020


Ответы (2)


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

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

Куча утилит на основе IDE для переименования полей обнаружит строковые константы с тем же именем, что и поле, и упомянет их («Эй, приятель? Может быть, ты хочешь, чтобы я переименовал и эту строковую константу здесь?»).

Если это все еще недостаточно для вас, вы можете сделать строковую константу и поместить ее сразу после. например:

public class Product{
    private String id;
    private String name;
    private String description;
    private static final String DESCRIPTION_PROPERTY_NAME = "description";
    // Getter and setter

}

а затем всегда используйте DESCRIPTION_PROPERTY_NAME вместо "description".

Конечно, никто не собирается редактировать что-то одно и забыть отредактировать то, что находится прямо на следующей строке!

Но если каким-то образом вы все еще беспокоитесь или это превратилось в какое-то сумасшедшее академическое упражнение - ну, в основном это проблема времени компиляции, поэтому используйте процессоры аннотаций, которые являются концепцией времени компиляции! Возможно, попробуйте FieldNameConstants ломбока.

И если даже это нехорошо, нет никакого реального способа - то, что вы сделали там с аспектом getDescription, в лучшем случае даст вам имя геттера. Не имя поля. В ванильной Java без процессора аннотаций без каких-либо других инструментов невозможно использовать языковую конструкцию, которая не сможет скомпилироваться, если поле будет переименовано. Существует синтаксис для ссылки на метод (x::y), но нет такой конструкции для ссылки на поле.

person rzwitserloot    schedule 28.08.2020
comment
@SanJaisy Также вам не следует напрямую использовать константу времени компиляции (например, DESCRIPTION_PROPERTY_NAME в данном случае), если вы ожидаете, что ее значение изменится. Вместо этого используйте геттер. - person Alex R; 29.08.2020
comment
@AlexR, конечно, буквальное имя поля никогда не может измениться. Это какой-то сумасшедший совет. - person rzwitserloot; 29.08.2020
comment
@rzwitserloot Я не говорю об имени поля, я говорю о значении "description". Если вы ожидаете, что оно изменится, вам не следует использовать константу времени компиляции. Потому что вам придется перекомпилировать все классы, которые его используют, если вы его измените. Что безумного в этом совете? Должно быть что-то, что он/она должен рассмотреть, имхо. - person Alex R; 29.08.2020
comment
@AlexR ты читал вопрос? Вы должны прочитать вопрос, прежде чем комментировать ответы. description — это строка, представляющая... имя поля. Если вы «не говорите» об имени поля, вы не говорите об этом вопросе. - person rzwitserloot; 29.08.2020
comment
@rzwitserloot Да, я это сделал, там написано, что имя поля description может измениться, и вы предложили присвоить имя поля DESCRIPTION_PROPERTY_NAME и использовать вместо него эту константу, или я что-то неправильно понял? - person Alex R; 29.08.2020
comment
@rzwitserloot Я просто хочу сказать, что вместо использования константы DESCRIPTION_PROPERTY_NAME вы должны использовать геттер, потому что, если вы измените значение DESCRIPTION_PROPERTY_NAME, вам придется перекомпилировать все классы, которые используют эту константу (и семантически это также не совсем константа). - person Alex R; 29.08.2020
comment
Да, вы что-то не так поняли. Он не может измениться во время выполнения. - person rzwitserloot; 30.08.2020

Вы можете попробовать использовать lombok для генерации имен полей в качестве постоянной переменной.

https://projectlombok.org/features/experimental/FieldNameConstants

@Data
@FieldNameConstants
public class Product{
    private String id;
    private String name;
    private String description;
}

Это будет сгенерировано во время компиляции:

public class Product {
    private String id;
    private String name;
    private String description;
  
  public static final class Fields {
    public static final String id = "id";
    public static final String name = "name";
    public static final String description = "description";
  }
  
  // getters and setters
}

И ссылка будет выглядеть так:

Query query = new Query();
if (productSearchCriteria.getDescription().isPresent()) {
    query.addCriteria(Criteria.where(Product.Fields.description).is(productSearchCriteria.getDescription().get()));
}

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

Никаких махинаций с отражением во время выполнения не требуется.

person George    schedule 28.08.2020
comment
Как я могу добавить экспериментальную функцию Lombok в весеннюю загрузку с помощью gradle? какая зависимость - person San Jaisy; 29.08.2020
comment
С другой стороны, некоторые люди считают ломбок по-своему махинацией. - person GhostCat; 29.08.2020
comment
Поскольку я не могу использовать запись, потому что запись нельзя использовать с Hibernate и JPA. Я нашел лучшее решение на данный момент - person San Jaisy; 29.08.2020