Преобразовать параметр T в соответствующий конкретный тип - PullRequest
2 голосов
/ 15 марта 2019

У меня есть абстрактный класс и два подкласса, которые его расширяют.

public abstract class StudentResponseReport<E> {
    private long id;
    private long roundId;
    // ...

    public abstract E getResponse();
}

public class StudentResponseReportSAQ extends StudentResponseReport<String> {
    // ...
}

public class StudentResponseReportMCQ extends StudentResponseReport<Collection<Integer>> {

}

Затем у меня есть универсальный метод с этой сигнатурой.

public <T> StudentResponseReport<?> convert(long roundId, T response) {
    // If T is a String, then create an instance of StudentResponseReportSAQ
    // If T is a Collection<Integer>, then create an instance of StudentResponseReportMCQ
}

Я хочу создатьсоответствующий подтип с учетом фактического параметра типа.

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

Каков наилучший способ достичь этого?

Ответы [ 3 ]

0 голосов
/ 15 марта 2019

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

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

В этом случае вам нужно иметь дело с StudentResponseReport<?> в коде вызова, но вы не можете проверить, какой это тип.Из ограничений обобщенных типов :

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

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

public Collection<StudentResponseReport<String>> convert(long roundId, String response) {
    // Create an instance of StudentResponseReportSAQ
}

public Collection<StudentResponseReport<Collection<Integer>> convert(long roundId, Collection<Integer> response) {
    // Create an instance of StudentResponseReportMCQ
}
0 голосов
/ 15 марта 2019
public <T> StudentResponseReport<T> convert(long roundId, T response) {
        // If T is a String, then create an instance of StudentResponseReportSAQ
        // If T is a Collection<Integer>, then create an instance of StudentResponseReportMCQ

        StudentResponseReport<T> srr = null;

        if (response instanceof String) {
            // T is a String, create StudentResponseReportSAQ

            return srr;
        }

        if (response instanceof Collection) {
            Collection c = (Collection) response;

            if (c.isEmpty()) {
                // optimistically assume that the type argument of the Collection is Integer as we can't verify it, create StudentResponseReportMCQ, or throw a runtime exception (I'll go with the former although the latters a better choice)
                return srr;
            } else if (c.stream().findAny().get().getClass().equals(Integer.class)) {
                // its a subtype of Collection<Integer>, create StudentResponseReportMCQ

                return srr;
            }
        }
        // unexpected type
        throw new RuntimeException("Unexpected Class!");

    }

Другие ответы хорошо объяснили, почему переопределение - это путь, и я его поддержу.

0 голосов
/ 15 марта 2019

Ответ в том, что вы не можете, по крайней мере, со сложными обобщенными типами, такими как Collection<Integer>.
Тип Integer не ограничен, это означает, что вы сможете увидеть его только как Object.

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

Лучшее, что вы можете получить, это что-то похожее на

private static final Map<Class<?>, Function<Object, Class<? extends StudentResponseReport<?>>>>
        TYPES = new IdentityHashMap<>();

static {
    TYPES.put(String.class, object -> StudentResponseReportSAQ.class);
    TYPES.put(Collection.class, object -> {
        final var collection = (Collection<?>) object;
        final var element = collection.stream()
                                      .findFirst()
                                      .orElse(null);

        if (element != null && Integer.class.isAssignableFrom(element.getClass())) {
            return StudentResponseReportMCQ.class;
        }

        throw new UnsupportedOperationException("...");
    });
}

private <T> StudentResponseReport<?> convert(
        final long roundId,
        final T response)
throws NoSuchMethodException,
       IllegalAccessException,
       InvocationTargetException,
       InstantiationException {
    for (final var entry : TYPES.entrySet()) {
        if (entry.getKey().isAssignableFrom(response.getClass())) {
            final var classToInstantiate = entry.getValue().apply(response);
            // Pass whatever you want to the constructor
            return classToInstantiate.getConstructor().newInstance();
        }
    }

    throw new UnsupportedOperationException("...");
}

И это только с двумя типами. Я действительно не рекомендую делать это.

...