代码之家  ›  专栏  ›  技术社区  ›  Ravindra Ranwala

将T参数转换为适当的混凝土类型

  •  1
  • Ravindra Ranwala  · 技术社区  · 7 年前

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

    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泛型和类型系统,不需要任何未检查的警告和强制转换,因为它们不是类型安全的。

    实现这一目标的最佳方式是什么?

    0 回复  |  直到 7 年前
        1
  •  0
  •   LppEdd    7 年前

    答案是你不能,至少对于复杂的泛型类型,比如 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("...");
    }
    

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

        2
  •  0
  •   ilinykhma    7 年前

    使用通配符作为返回类型似乎不是个好主意。根据 wildcard guidelines

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

    在这种情况下,你必须处理 StudentResponseReport<?> 在调用代码中,但无法验证是哪种类型。从…起 restrictions of generics :

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

    我认为重载是最合适的解决方案,如果它能应用于你的情况。尽管它很冗长。

    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
    }
    
        3
  •  0
  •   Ryotsu    7 年前
    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!");
    
        }
    

    其他答案很好地解释了为什么压倒一切才是正确的选择,我会支持它。