我有一个抽象类和两个扩展它的子类。
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泛型和类型系统,并且不希望任何未经检查的警告和强制类型转换,因为它们不安全。
实现此目标的最佳方法是什么?
答案 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!");
}
其他答案已经很好地解释了为什么要采用覆盖是必不可少的方法,我将其列为第二。