泛型地狱:我可以构造一个 TypeLiteral<Set<T>>使用泛型?

2022-01-17 00:00:00 set types java generics guice

我能够使以下通用方法工作的唯一方法是传递看似多余的 TypeLiteral<Set<T>> 参数.我相信应该可以在给定另一个参数的情况下以编程方式构造这个参数,但不知道如何.

The only way I was able to get the below generic method to work was to pass the seemingly redundant TypeLiteral<Set<T>> parameter. I believe it should be possible to construct this parameter programmatically given the other parameter, but can't figure out how.

protected <T> Key<Set<T>> bindMultibinder(
 TypeLiteral<Set<T>> superClassSet, TypeLiteral<T> superClass) {
   final Key<Set<T>> multibinderKey = Key.get(superClassSet, randomAnnotation);
   return multibinderKey;
}

客户端代码如下:

bindMultibinder(new TypeLiteral<Set<A<B>>>(){}, new TypeLiteral<A<B>>(){});

其中 A 和 B 是接口.

Where A and B are interfaces.

如果我尝试以下操作(删除 TypeLiteral<Set<T>> superClassSet 参数),我会得到一个 java.util.Set<T>不能用作钥匙;它没有完全指定.运行时错误.

If I try the following (removing the TypeLiteral<Set<T>> superClassSet parameter), I get a java.util.Set<T> cannot be used as a key; It is not fully specified. runtime error.

protected <T> Key<Set<T>> bindMultibinder(TypeLiteral<T> superClass) {
   final Key<Set<T>> multibinderKey = Key.get(
    new TypeLiteral<Set<T>>() {}, randomAnnotation);
   return multibinderKey;
}

推荐答案

完全指定表示所有类型参数的值都是已知的.使用 Guice 公共 API 从 TypeLiteral<T> 构造完全指定的 TypeLiteral<Set<T>> 似乎是不可能的.具体来说,TypeLiteral 只有两个构造函数.第一个是:

Fully specified means that the values of all type parameters are known. Constructing a fully specified TypeLiteral<Set<T>> from a TypeLiteral<T> appears to be impossible using the Guice public API. Specifically, TypeLiteral only has two constructors. The first is:

/**
 * Constructs a new type literal. Derives represented class from type
 * parameter.
 *
 * <p>Clients create an empty anonymous subclass. Doing so embeds the type
 * parameter in the anonymous class's type hierarchy so we can reconstitute it
 * at runtime despite erasure.
 */
@SuppressWarnings("unchecked")
protected TypeLiteral() {
  this.type = getSuperclassTypeParameter(getClass());
  this.rawType = (Class<? super T>) MoreTypes.getRawType(type);
  this.hashCode = type.hashCode();
}

此构造函数尝试从 TypeLiteral 的运行时类推断类型参数的值.仅当运行时类确定类型​​参数时,这将产生一个完全指定类型.但是,因为泛型类的所有实例共享同一个运行时类(即 new HashSet().getClass() == new HashSet().getClass(),类型参数只有在 TypeLiteral 的 non-generic 子类被实例化时才知道.也就是说,我们不能为 的不同值重用相同的类声明T,但必须为每个 T 定义一个新类.正如 alf 的回答所表明的那样,这相当麻烦.

This constructor attempts to deduce the values of type parameters from the TypeLiteral's runtime class. This will yield a fully specified type only if the runtime class determines the type parameter. However, because all instances of a generic class share the same runtime class (that is, new HashSet<String>().getClass() == new HashSet<Integer>().getClass(), the type parameter is only known if a non-generic subclass of TypeLiteral is instantiated. That is, we can't reuse the same class declaration for different values of T, but must define a new class for each T. This is rather cumbersome, as alf's answer demonstrates.

这给我们留下了另一个构造函数,它更有帮助,但不是公共 API 的一部分:

This leaves us with the other constructor, which is more helpful, but not part of the public API:

/**
 * Unsafe. Constructs a type literal manually.
 */
@SuppressWarnings("unchecked")
TypeLiteral(Type type) {
  this.type = canonicalize(checkNotNull(type, "type"));
  this.rawType = (Class<? super T>) MoreTypes.getRawType(this.type);
  this.hashCode = this.type.hashCode();
}

我们可以这样使用这个构造函数:

We can use this constructor as follows:

package com.google.inject;

import java.util.Set;

import com.google.inject.internal.MoreTypes;

public class Types {
    public static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> lit) {
        return new TypeLiteral<Set<T>>(new MoreTypes.ParameterizedTypeImpl(null, Set.class, lit.getType())); 
    }
}

测试用例:

public static void main(String[] args) {
    System.out.println(setOf(new TypeLiteral<String>() {}));
}

在一个完美的世界里,Guice 会提供一个公共 API 来完成这个......

In a perfect world, Guice would offer a public API to accomplish this ...

相关文章