Jef*_*rod 18 java generics types set guice
我能够使下面的通用方法工作的唯一方法是传递看似冗余的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;
}
Run Code Online (Sandbox Code Playgroud)
客户端代码如下:
bindMultibinder(new TypeLiteral<Set<A<B>>>(){}, new TypeLiteral<A<B>>(){});
Run Code Online (Sandbox Code Playgroud)
其中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;
}
Run Code Online (Sandbox Code Playgroud)
alf*_*alf 15
如果您已经知道答案的大部分内容,请原谅我:在您的级别上很难做出假设.
问题的原因是类型擦除,如您所知.为了摆脱类型擦除,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());
}
}
Run Code Online (Sandbox Code Playgroud)
Point是,当您创建一个扩展通用超类并显式指定类型参数的类时,您通常需要编写接受该特定类型的metod,并且这些方法的签名不能被删除.在这种情况下,我们在FAQ中讨论没有问题,但编译器保存了类型信息:无论如何,类的用户需要知道确切的类型才能使用这些方法.
现在你的版本没有继承的具体类TypeLiteral<Set<YourSpecificType>>,它只有TypeLiteral<Set<T>>- 而且它就是全部失败的地方.
改变我的小例子,那将是:
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());
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我们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;
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,现在保留了类型信息.我相信这种方法没有被使用,因为即使对于这个选秀,它也太痛苦了.
mer*_*ike 12
完全指定意味着所有类型参数的值都是已知的.使用Guice公共API 构建一个完全指定TypeLiteral<Set<T>>的TypeLiteral<T>似乎是不可能的.具体来说,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();
}
Run Code Online (Sandbox Code Playgroud)
此构造函数尝试从TypeLiteral运行时类中推导出类型参数的值.仅当运行时类确定类型参数时,才会生成完全指定的类型.但是,因为泛型类的所有实例共享相同的运行时类(也就是说,new HashSet<String>().getClass() == new HashSet<Integer>().getClass()只有在实例化非泛型子类时才知道类型参数TypeLiteral.也就是说,我们不能为不同的值重用相同的类声明的T,但必须为每个新的类T,这是相当麻烦的,如ALF的回答证明.
这给我们留下了另一个构造函数,它更有用,但不是公共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();
}
Run Code Online (Sandbox Code Playgroud)
我们可以使用这个构造函数如下:
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()));
}
}
Run Code Online (Sandbox Code Playgroud)
测试用例:
public static void main(String[] args) {
System.out.println(setOf(new TypeLiteral<String>() {}));
}
Run Code Online (Sandbox Code Playgroud)
在完美的世界中,Guice将提供一个公共API来实现这一目标......