首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >泛型地狱:我能用泛型构造一个TypeLiteral<Set<T>>吗?

泛型地狱:我能用泛型构造一个TypeLiteral<Set<T>>吗?
EN

Stack Overflow用户
提问于 2012-01-08 03:49:16
回答 2查看 5.2K关注 0票数 18

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

代码语言:javascript
复制
protected <T> Key<Set<T>> bindMultibinder(
 TypeLiteral<Set<T>> superClassSet, TypeLiteral<T> superClass) {
   final Key<Set<T>> multibinderKey = Key.get(superClassSet, randomAnnotation);
   return multibinderKey;
}

客户端代码如下所示:

代码语言:javascript
复制
bindMultibinder(new TypeLiteral<Set<A<B>>>(){}, new TypeLiteral<A<B>>(){});

其中A和B是接口。

如果我尝试执行以下操作(删除TypeLiteral<Set<T>> superClassSet参数),则会得到一个java.util.Set<T> cannot be used as a key; It is not fully specified.运行时错误。

代码语言:javascript
复制
protected <T> Key<Set<T>> bindMultibinder(TypeLiteral<T> superClass) {
   final Key<Set<T>> multibinderKey = Key.get(
    new TypeLiteral<Set<T>>() {}, randomAnnotation);
   return multibinderKey;
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-01-08 07:18:00

完全指定意味着所有类型参数的值都是已知的。使用Guice public API从TypeLiteral<T>构造完全指定的TypeLiteral<Set<T>>似乎是不可能的。具体地说,TypeLiteral只有两个构造函数。第一个是:

代码语言:javascript
复制
/**
 * 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<String>().getClass() == new HashSet<Integer>().getClass() ),所以仅当TypeLiteral的非泛型子类被实例化时才知道类型参数。也就是说,我们不能对不同的T值重复使用相同的类声明,但必须为每个T定义一个新类。正如阿尔夫的回答所展示的那样,这相当麻烦。

这让我们使用另一个构造函数,它更有帮助,但不是公共API的一部分:

代码语言:javascript
复制
/**
 * 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();
}

我们可以按如下方式使用此构造函数:

代码语言:javascript
复制
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())); 
    }
}

测试用例:

代码语言:javascript
复制
public static void main(String[] args) {
    System.out.println(setOf(new TypeLiteral<String>() {}));
}

在理想的情况下,Guice会提供一个公共API来实现这一点……

票数 13
EN

Stack Overflow用户

发布于 2012-01-08 04:41:09

如果你已经知道了大部分答案,请原谅我:在你的水平上很难做出假设。

正如您已经知道的,问题的原因是类型擦除。为了摆脱类型擦除,Guice对具体的祖先使用了一个技巧,如下所示:

代码语言:javascript
复制
class Trick<T> {
    T t;
}

public class GenericTest {
    public static void main(String[] args) {
        Trick<Set<String>> trick = new Trick<Set<String>>() {
        };

        // Prints "class org.acm.afilippov.GenericTest$1"
        System.out.println(trick.getClass());
        // Prints "org.acm.afilippov.Trick<java.util.Set<java.lang.String>>"
        System.out.println(trick.getClass().getGenericSuperclass());
    }
}

要点是,当您创建一个扩展泛型超类并显式指定类型参数的类时,通常需要编写metods which accept that very specific type, and these methods' signatures cannot be erased。在这种情况下,我们在FAQ中没有讨论任何问题,但是编译器无论如何都会保存类型信息:您的类的用户需要知道确切的类型才能使用这些方法。

现在您的版本没有从TypeLiteral<Set<YourSpecificType>>继承的具体类,它只有TypeLiteral<Set<T>>-and,这就是它失败的地方。

改变我的小例子,那就是:

代码语言:javascript
复制
public class GenericTest {
    public static void main(String[] args) {
        tryMe(String.class);
    }

    private static <T> void tryMe(Class<T> clazz) {
        Trick<Set<T>> trick = new Trick<Set<T>>() {
        };

        // Prints "class org.acm.afilippov.GenericTest$1"
        System.out.println(trick.getClass());
        // Prints "org.acm.afilippov.Trick<java.util.Set<T>>"
        System.out.println(trick.getClass().getGenericSuperclass());
    }
}

如您所见,我们的GenericTest$1不再是具体的:它仍然有一个类型参数,并且它的具体值(这里是String )在编译过程中丢失了。

当然,您可以避免这种情况,但为了做到这一点,您需要创建一个具有用于继承的特定类型参数的类-以便Guice能够确定细节。稍等片刻,我会试着想出一个例子。

更新:它被证明是一个非常长的比特。下面是一个更新的版本:

代码语言:javascript
复制
public class GenericTest {
    public static void main(String[] args) throws Exception {
        tryMe(String.class);
    }

    private static <T> void tryMe(Class<T> clazz) throws IllegalAccessException, InstantiationException {
        Class c = loadClass("org.acm.afilippov.ASMTrick", generateClass(clazz));

        Trick<Set<T>> trick = (Trick<Set<T>>) c.newInstance();

        // Prints "class org.acm.afilippov.ASMTrick"
        System.out.println(trick.getClass());
        // Prints "org.acm.afilippov.Trick<java.util.Set<java.lang.String>>"
        System.out.println(trick.getClass().getGenericSuperclass());
    }

    private static byte[] generateClass(Class<?> element) {
        ClassWriter cw = new ClassWriter(0);
        MethodVisitor mv;

        cw.visit(V1_6, ACC_FINAL + ACC_SUPER, "org/acm/afilippov/ASMTrick",
                "Lorg/acm/afilippov/Trick<Ljava/util/Set<L" + element.getName().replaceAll("\\.", "/") + ";>;>;",
                "org/acm/afilippov/Trick", null);

        {
            mv = cw.visitMethod(0, "<init>", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "org/acm/afilippov/Trick", "<init>", "()V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }

    private static Class loadClass(String className, byte[] b) {
        //override classDefine (as it is protected) and define the class.
        Class clazz = null;
        try {
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            Class cls = Class.forName("java.lang.ClassLoader");
            java.lang.reflect.Method method =
                    cls.getDeclaredMethod("defineClass", new Class[]{String.class, byte[].class, int.class, int.class});

            // protected method invocaton
            method.setAccessible(true);
            try {
                Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length)};
                clazz = (Class) method.invoke(loader, args);
            } finally {
                method.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        return clazz;
    }
}

如您所见,类型信息现在得到了保留。我相信这种方法不会被使用,因为即使对于这个草案来说,它也太痛苦了。

票数 15
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8772555

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档