Проблемы типизации

112
30 января 2021, 09:10

Итак, у меня есть сущности, несколько штук.

InputField
CheckBoxField
SelectField

Все они наследуются от одной абстрактной сущности AbstractEntity. Для каждой сущности есть репозиторий, наследующийся от CommonRepository:

@NoRepositoryBean
public interface CommonRepository<E extends AbstractEntity> extends JpaRepository<E, Long>

Я заавтовайрил репозитории в сервис:

@Service
public class ShiftServiceImpl implements ShiftService

и положил в мапу <дискриминатор, бин репозитория>, вот так:

private Map<String, CommonRepository> fieldRepositories;
fieldRepositories = new HashMap<>();
fieldRepositories.put("input", inputFieldRepository);
fieldRepositories.put("checkbox", checkBoxFieldRepository);
fieldRepositories.put("select", selectFieldRepository);

Далее, мне приходит дискриминатор сущности в виде String. И, определив по дискриминатору, что за сущность, я хочу дёрнуть нужную мне сущность через соответствующий бин репозитория. То есть, так:

fieldRepositories.get(e.get("type"))
                            .findById(id)
                            .ifPresent(i -> entities.add(i));

При этом, IDEA не даёт мне это сделать, подчёркивает последнюю i пишет:

add (ru.field.AbstractField) in List cannot be applied to
    (java.lang.Object)

Подскажите, как правильно типизировать класс / мапу, чтобы всё работало?

UPD:

Заработало, благодаря ответу коллеги, но теперь не до конца чекает тип (Unchecked cast):

UPD2:

Помог комментарий, но теперь отвалилось в другом месте, при сохранении сущности:

    entities
            .forEach(e -> {
                fieldRepositories.get(e.getFieldType().getName()).save(e);
            });

Answer 1

Насколько я понял все репозитории в fieldRepositories работают с сущностями-наследниками AbstractField, а entities — коллекция AbstractField.

Ошибка заключается в том, что из fieldRepositories это никак не ясно, ведь в него можно добавить другой репозиторий, у которого findById вернет объект, никак не связанный с AbstractField.

Исправить можно так:

private Map<String, CommonRepository<? extends AbstractField>> fieldRepositories;

Теперь в fieldRepositories не получится добавить «неправильный» объект — произойдет ошибка компиляции.

ОБНОВЛЕНИЕ: Да, проблема с типизацией тут глубже. Ошибка в этом коде:

entities
            .forEach(e -> {
                fieldRepositories.get(e.getFieldType().getName()).save(e);
            });

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

Например, по ошибке ключ в словаре может отличаться от e.getFieldType().getName(). Или где-то между сбором и сохранением сущностей Вы можете сделать что-то вроде fieldRepositories.put("input", checkBoxFieldRepository);

Знать, что сопоставление сущность—репозиторий правильное компилятор не может. Этой информации в коде нет. Правильность зависит только от внимательности разработчика, который заполняет Map. Т.ч. боюсь, обойтись совсем без приведения типов здесь не получится.

Заставить компилятор проглотить ошибку можно так:

entities
            .forEach(e -> {
                ((CommonRepository<AbstractField>)fieldRepositories.get(e.getFieldType().getName())).save(e);
            });

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

//объявление возвращаем, т.к. приведение вынесено в метод
private Map<String, CommonRepository> fieldRepositories;
/**
* Отдельный метод для получения репозитория типа `AbstractField`.
* Так приведение будет только в одном месте. 
* Предупреждение компилятора гасим аннотацией.
*/        
@SuppressWarnings("unchecked")
CommonRepository<AbstractField> getRepository(Map<String, CommonRepository> map, String key) {
    return (CommonRepository<AbstractField>) map.get(key);
}
//Примеры использования
//поиск
getRepository(e.get("type"))
                            .findById(id)
                            .ifPresent(i -> entities.add(i));
//сохранение
entities.forEach(e -> {
                getRepository(e.getFieldType().getName()).save(e);
            });

Строго типизированное решение мне сейчас в голову не приходит. Может кто-нибудь предложит какой-нибудь изощренный вариант.

READ ALSO
ServerSocket Java Установка любого адреса

ServerSocket Java Установка любого адреса

Подскажите пожалуйста, есть ли возможность установить произвольный (а не только localhost) адрес, который будет приниматься в

85
Spring. Как корректно читать значения из файла конфигурации?

Spring. Как корректно читать значения из файла конфигурации?

Есть файл конфигурации src\main\resources\applicationproperties

123
добавление ссылок в TableView

добавление ссылок в TableView

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

83