泛型方法的通用返回类型

8

Consider a generic method definition like this:

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

这个方法可以很好地获取任何类型的对象,但是对于像List<String>这样的泛型对象,就会出现问题。我发现唯一能让它正常工作的方法是这样的:

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

但是这需要额外的强制类型转换,这就使得原本的方法失去了它的作用。有没有一种方式可以将List<String>.class或类似的内容提供给该方法?(我知道类型擦除会阻止List<String>的存在,但也许有一种方法可以解决)。注意:所谓“使其工作”,指的是在不产生类型安全警告的情况下运行代码。我知道这个代码可以按照现在的形式运行,但它不是类型安全的。

这不是一种类型安全的方式。但是这将起作用 List<String> parameter1 = getParameter(List.class); - Jatin
1
为什么需要进行类型转换?我尝试了不进行类型转换的操作,但是只得到了一个有关类型安全性的警告。 - Andrei Vajna II
@AndreiVajnaII 我想要一种类型安全的方式;-)。 - Juru
你所做的事情在我看来似乎并没有更加类型安全。在你的示例中,除了推断泛型方法的类型之外,你并没有使用该类。在方法之外进行转换表达式与泛型编译后得到的结果完全相同。 - Radiodef
2个回答

7
没有修改下游代码(context.get()),你无法以类型安全的方式完成此操作,因为泛型类型在运行时被擦除。实际上,目前根本没有使用clazz参数:你可以直接删除该参数,该方法将执行相同的操作,因为由于擦除,对(T)的转换目前是一个无操作。即使对于非泛型类,您当前的代码也不是类型安全的。 所以,如果你不关心安全性,只想避免客户端代码中的强制类型转换,那么你可以删除最后一个参数,并在该方法上一次抑制警告。
如果你想要类型安全性,可以使用超类型令牌来保留泛型类型信息: Guava的TypeToken可以工作,并且很容易获得(我认为每个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"),但是在这里它是可证明类型安全的,因为您正在维护类型信息(在许多泛型类的内部,您将找到可证明的安全转换,因此即使在正确代码中也无法避免)。

1

正如你所说,没有像 List<String>.class 这样的东西。因此,如果你只想读取参数而不需要转换,可以使用类似以下代码:

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

我假设你的上下文也返回Object,而且你需要为那个方法抑制类型安全检查。

2
有一种类似于List<String>.class的东西,通常被称为“超类型令牌”。 - BeeOnRope
即使您提供了这样的“数据结构”,也无法强制执行任何 List 实例的类型安全性,因为列表的“类型”信息会被删除。那么这有什么意义呢? - sodik
1
你重复了问题中提到的观点。我不明白这个答案如何有所帮助。 - Jatin
我的签名在使用时不需要强制转换,例如 List<String> test = getParameter(ctx, "p"); 可以编译通过。 - sodik
1
@BeeOnRope 如果您需要额外存储它,那当然可以。另外,使用 Class<T>.cast() 可以消除那个压制警告,怎么样? - sodik
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接