将T参数转换为适当的具体类型

时间:2019-03-15 14:33:14

标签: java generics polymorphism

我有一个抽象类和两个扩展它的子类。

List<String> allLines = Files.readAllLines(path,StandardCharsets.ISO_8859_1);

然后我有一个带有此签名的通用方法。

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>> {

}

我想要的是在给定实际类型参数的情况下创建相应的子类型。

我需要使用Java泛型和类型系统,并且不希望任何未经检查的警告和强制类型转换,因为它们不安全。

实现此目标的最佳方法是什么?

3 个答案:

答案 0 :(得分:0)

答案是,至少不能使用复杂的通用类型,例如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("...");
}

只有两种类型。我真的不建议这样做。

答案 1 :(得分:0)

使用通配符作为返回类型似乎不是一个好主意。根据{{​​3}}

  

应避免使用通配符作为返回类型,因为它会强制   程序员使用代码处理通配符。

在这种情况下,您必须在调用代码中处理StudentResponseReport<?>,但是无法验证它是哪种类型。来自wildcard guidelines

  

您无法验证泛型的哪种参数化类型   在运行时使用

我认为重载是最合适的解决方案,如果可以在您的情况下应用它。尽管它很冗长。

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
}

答案 2 :(得分:0)

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!");

    }

其他答案已经很好地解释了为什么要采用覆盖是必不可少的方法,我将其列为第二。