Java如何使用Set参数化泛型方法?

Jan*_*nek 5 java generics

我有一个这样的签名方法:

private <T> Map<String, byte[]> m(Map<String, T> data, Class<T> type)
Run Code Online (Sandbox Code Playgroud)

当我这样调用时,它工作正常:

Map<String, String> abc= null;
m(abc, String.class);
Run Code Online (Sandbox Code Playgroud)

但是当我的参数T是一个Set时它不起作用:

Map<String, Set<String>> abc= null;
m(abc, Set.class);
Run Code Online (Sandbox Code Playgroud)

有没有办法让它发挥作用?

Viv*_*ath 3

你将不得不做一些非常丑陋的事情,使用未经检查的强制转换,如下所示:

m(abc, (Class<Set<String>>) (Class<?>) Set.class);
Run Code Online (Sandbox Code Playgroud)

这归结为类型擦除。在运行时Class<Set<String>>与 相同Class<Set<Integer>>,因为我们没有具体化泛型,因此无法知道您拥有的是“字符串集”的类还是“整数集”的类。

我前段时间问过一个相关问题,也应该给你一些指导:

在我看来,这种混乱是由于仿制药是在事后附加的,并且没有具体化。当编译器告诉您泛型类型不匹配,但您甚至没有一种简单的方法来表示该特定类型时,我认为这是语言的失败。例如,在您的情况下,您最终会遇到编译时错误:

        m(abc, Set.class);
        ^
  required: Map<String,T>,Class<T>
  found: Map<String,Set<String>>,Class<Set>
  reason: inferred type does not conform to equality constraint(s)
    inferred: Set
    equality constraints(s): Set,Set<String>
  where T is a type-variable:
    T extends Object declared in method <T>m(Map<String,T>,Class<T>)
Run Code Online (Sandbox Code Playgroud)

现在,您认为“哦,我应该使用Set<String>.classthen”是完全合理的,但这是不合法的。这是语言中泛型实现的抽象泄漏,特别是它们会受到类型擦除的影响。从语义上讲,Set<String>.class表示一组字符串的运行时类实例。但实际上在运行时我们无法表示一组字符串的运行时类,因为它与包含任何其他类型的对象的集合无法区分。

因此,我们有一个与编译时语义不一致的运行时语义,并且要知道为什么 Set<T>.class不合法需要知道泛型在运行时没有具体化。这种不匹配导致了像这样奇怪的解决方法。

使问题变得更加复杂的是,类实例最终也与类型标记混为一谈。由于您无法在运行时访问泛型参数的类型,因此解决方法是传入 类型的参数Class<T>。从表面上看,这很有效,因为您可以传递诸如String.class(类型为Class<String>)之类的内容,并且编译器很高兴。但这种方法在您的情况下会崩溃:如果T 它本身代表一个具有自己的泛型类型参数的类型怎么办?现在使用类作为类型标记是没有用的,因为没有办法区分它们Class<Set<String>>,而且Class<Set<Integer>>从根本上讲,它们都Set.class在运行时,因此共享相同的类实例。因此,在我看来,使用类作为运行时类型令牌并不能作为通用解决方案。

由于该语言的这一缺点,有一些库可以非常轻松地检索通用类型信息。此外,他们还提供了更好地表示事物“类型”的类: