【问题标题】:Generic return type from a generic method泛型方法的泛型返回类型
【发布时间】:2015-02-17 09:49:58
【问题描述】:

考虑这样的通用方法定义:

public static <T> T getParameter(final Context context, final ContextParameter contextParameter, final Class<T> clazz) {
    return (T) context.get(contextParameter.name());
}

在您尝试像 List 这样的通用对象之前,它可以完美地获取任何类型的对象。我可以让它工作的唯一方法是这样的:

 final List<String> fileNames = 
             (List<String>) ContextUtils.getParameter(context,
                             ContextParameter.EOF_FILE_NAMES_TO_ZIP, List.class);

但这需要额外的演员表,从而消除了拥有该方法的目的。有没有办法能够提供List&lt;String&gt;.class 或类似于该方法的东西? (我知道类型擦除会阻止 List&lt;String&gt; 存在,但也许有办法做到这一点。

注意:要让它工作,我的意思是没有类型安全警告。我知道代码可以按原样工作,但它不是类型安全的。

【问题讨论】:

  • 不是类型安全的方式。但这会工作List&lt;String&gt; parameter1 = getParameter(List.class);
  • 为什么需要投射?我在没有演员表的情况下尝试了它,我得到的只是关于类型安全的警告。
  • @AndreiVajnaII 我想要一种类型安全的方式 ;-)。
  • 在我看来,您所做的对于 Class 来说不再是类型安全的了。在您的示例中,除了推断泛型方法的类型之外,您没有使用 Class 。方法外的强制转换表达式与泛型将被编译成的完全一样。

标签: java generics type-erasure


【解决方案1】:

在不修改下游代码 (context.get()) 的情况下,您无法以类型安全的方式执行此操作,因为泛型类型在运行时会被删除。事实上,目前 clazz 参数根本没有被使用:您可以简单地删除该参数,并且该方法将执行相同的操作,因为由于擦除,当前对 (T) 的强制转换是无操作的。您当前的代码即使对于非泛型类也不是类型安全的。

因此,如果您不关心安全性,只是想避免在客户端代码中进行强制转换,您可以删除最后一个参数,并在方法上取消一次警告。

如果你想要类型安全,你可以使用超类型标记来保存泛型类型信息:Guava 的 TypeToken 工作并且很容易获得(IMO 每个 Java 项目都应该已经使用 Guava)。

但是,这将需要对您的代码进行下游更改 - 因为您需要在添加对象时捕获类型信息并在它们出现时检查它。您没有共享 Context 类的代码,所以很难说这是否可能,但无论如何您都想要这样的东西:

static class TypedParam {
    final TypeToken<?> type;
    final Object object;
    private TypedParam(TypeToken<?> type, Object object) {
        this.type = type;
        this.object = object;
    }
}

public static class Context {
    Map<String, TypedParam> params = new HashMap<>();
    TypedParam get(String name) {
        return params.get(name);
    }
}

public static <T> T getParameter(final Context context, final String name, final TypeToken<T> type) {
    TypedParam param = context.get(name);
    if (type.isAssignableFrom(param.type)) {
        @SuppressWarnings("unchecked")
        T ret = (T)param.object;
        return ret;
    } else {
        throw new ClassCastException(param.type + " cannot be assigned to " + type);
    }
}

基本上,您知道参数映射中所有对象的泛型类型,并在运行时检查“泛型感知转换”。我上面没有包含Context.put(),但是你需要在添加参数时捕获类型信息。

你仍然有一个@SuppressWarnings("unchecked"),但在这里它可以证明是类型安全的,因为你要维护类型信息(在许多泛型类的胆量中,你会发现可以证明是安全的类型转换,所以即使它们也不能总是避免用正确的代码)。

【讨论】:

    【解决方案2】:

    正如你已经说过的,没有什么比 List&lt;String&gt;.class 更重要的了。因此,如果您只想读取不带强制转换的参数,您可以使用以下内容:

    public static <T> T getParameter(final Map<String, Object> context, final String name) {
        return (T) context.get(name);
    }
    

    我假设您的上下文也返回 Object,并且无论如何您都需要禁止对该方法进行类型安全检查。

    【讨论】:

    • 有类似 List.class 的东西 - 它通常被称为“超级类型令牌”。
    • 不是直接在语言中。即使您提供这样的“数据结构”,您也无法强制任何List 实例的类型安全,因为有关列表“类型”的信息已被删除。那有什么意义呢?
    • 您重复了上述问题。我看不出这个答案有什么帮助。
    • 我的签名在使用时不需要强制转换,例如List&lt;String&gt; test = getParameter(ctx, "p"); 编译干净
    • @BeeOnRope 如果您另外存储它,那么当然可以。顺便提一句。使用Class&lt;T&gt;.cast() 摆脱抑制警告怎么样?
    猜你喜欢
    • 2016-12-24
    • 1970-01-01
    • 2011-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多