Generics Hell:我可以使用generics构建TypeLiteral <Set >吗?

我能够使下面的generics方法工作的唯一方法是传递看似冗余的TypeLiteral<Set>参数。 我认为应该可以在给定其他参数的情况下以编程方式构造此参数,但无法弄清楚如何。

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

客户端代码如下:

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

其中A和B是接口。

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

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

完全指定意味着所有类型参数的值都是已知的。 使用Guice公共API,从TypeLiteral构造完全指定的TypeLiteral>似乎是不可能的。 具体来说, TypeLiteral只有两个构造函数。 首先是:

 /** * Constructs a new type literal. Derives represented class from type * parameter. * * 

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) MoreTypes.getRawType(type); this.hashCode = type.hashCode(); }

此构造函数尝试从TypeLiteral的运行时类中推导出类型参数的值。 仅当运行时类确定类型​​参数时,才会生成完全指定的类型。 但是,因为generics类的所有实例共享相同的运行时类(即, new HashSet().getClass() == new HashSet().getClass() ,所以只有实例化TypeLiteral非generics子类。也就是说,我们不能为T不同值重用相同的类声明,但必须为每个T定义一个新类。这是相当麻烦的,正如alf的答案所示。

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

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

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

 package com.google.inject; import java.util.Set; import com.google.inject.internal.MoreTypes; public class Types { public static  TypeLiteral> setOf(TypeLiteral lit) { return new TypeLiteral>(new MoreTypes.ParameterizedTypeImpl(null, Set.class, lit.getType())); } } 

测试用例:

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

在完美的世界中,Guice将提供一个公共API来实现这一目标……

如果您已经了解了大部分答案,请原谅我:在您的级别上很难做出假设。

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

 class Trick { T t; } public class GenericTest { public static void main(String[] args) { Trick> trick = new Trick>() { }; // Prints "class org.acm.afilippov.GenericTest$1" System.out.println(trick.getClass()); // Prints "org.acm.afilippov.Trick>" System.out.println(trick.getClass().getGenericSuperclass()); } } 

要点是,当您创建一个扩展通用超类并明确指定类型参数的类时,您通常需要编写接受该特定类型的metod,并且这些方法的签名不能被删除 。 在这种情况下,我们在FAQ中讨论没有问题,但编译器保存类型信息,无论如何:类的用户需要知道确切的类型才能使用这些方法。

现在你的版本没有从TypeLiteral>inheritance的具体类,它只有TypeLiteral> – 这就是它全部失败的地方。

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

 public class GenericTest { public static void main(String[] args) { tryMe(String.class); } private static  void tryMe(Class clazz) { Trick> trick = new Trick>() { }; // Prints "class org.acm.afilippov.GenericTest$1" System.out.println(trick.getClass()); // Prints "org.acm.afilippov.Trick>" System.out.println(trick.getClass().getGenericSuperclass()); } } 

正如您所看到的,我们的GenericTest$1不再具体了:它仍然有一个类型参数,其具体值(此处为String )在编译期间丢失。

你当然可以避免这种情况,但为了做到这一点,你需要创建一个具有用于inheritance的特定类型参数的类 – 这样Guice就能够计算出细节。 稍等一下,我会试着想出一个例子。

更新:结果是非常长的。 所以这是一个更新版本:

 public class GenericTest { public static void main(String[] args) throws Exception { tryMe(String.class); } private static  void tryMe(Class clazz) throws IllegalAccessException, InstantiationException { Class c = loadClass("org.acm.afilippov.ASMTrick", generateClass(clazz)); Trick> trick = (Trick>) c.newInstance(); // Prints "class org.acm.afilippov.ASMTrick" System.out.println(trick.getClass()); // Prints "org.acm.afilippov.Trick>" 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;>;", "org/acm/afilippov/Trick", null); { mv = cw.visitMethod(0, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "org/acm/afilippov/Trick", "", "()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; } } 

如您所见,现在保留了类型信息。 我相信这种方法没有被使用,因为即使对于这个选秀,它也太痛苦了。