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

  显示原文与译文双语对照的内容

我能够获得下面一般方法的唯一方法是传递似乎冗余的TypeLiteral<Set<T>> 参数。 我相信应该以编程方式编写这个参数,但是不能解决这个参数。


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 是接口。

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


protected <T> Key<Set<T>> bindMultibinder(TypeLiteral<T> superClass) {


 final Key<Set<T>> multibinderKey = Key.get(


 new TypeLiteral<Set<T>>() {}, randomAnnotation);


 return multibinderKey;


}



时间:

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


/**


 * 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 定义一个新类。 这是相当麻烦的,alf的回答。

这使我们有了另一个构造函数,它更有用,但不是 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();


}



我们可以使用这里构造函数,如下所示:


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会提供一个 public API来完成这个任务。

如果你已经了解了大部分答案,请原谅我: 对你的水平做一个假设是很困难的。

问题的原因是类型擦除,就像你所知道的。 为了消除类型擦除,Guice使用了一个与具体祖先的技巧,如下所示:


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());


 }


}



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

现在你的版本没有继承的具体类 TypeLiteral<Set<YourSpecificType>> 它只有 TypeLiteral<Set<T>> —and,它是所有失败的地方。

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


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将能够计算出具体的细节。 等等,我试试一个例子。

更新:结果是很长的一段时间。 以下是你的更新版本:


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;


 }


}



你可以看到,类型信息现在被保存。 我相信这种方法不被使用,因为它对于这个草稿太痛苦了。

...