将泛型添加到1.5中时,java.lang.reflect
添加了一个Type
接口,该接口具有各种子类型来表示类型。 Class
进行了改进,以针对1.5之前的类型实现Type
。 Type
子类型适用于从1.5开始的新通用类型。
这一切都很好。必须Type
才能执行任何有用的操作,但可以通过试用,错误,修改和(自动)测试进行操作,这有点尴尬。除了执行方面...
应如何实施equals
和hashCode
。 ParameterizedType
的{{1}}子类型的API描述如下:
实现此接口的类的实例必须实现equals()方法,该方法使共享相同泛型类型声明并具有相同类型参数的任何两个实例相等。
(我想这是Type
和getActualTypeArguments
的意思,而不是getRawType
?)
我们从getOwnerType
的总协定中知道,java.lang.Object
也必须实现,但是似乎没有说明该方法应产生什么值的规定。
hashCode
的其他子类型似乎都没有提到Type
或equals
,只是hashCode
的每个值都有不同的实例。
那我应该在Class
和equals
中放入什么?
(如果您想知道,我尝试用类型参数代替实际类型。因此,如果我在运行时知道 hashCode
TypeVariable<?>
是T
Class<?>
,然后我想替换String
,因此Type
变成List<T>
,List<String>
变成T[]
,String[]
(可能会发生!)变成List<T>[]
,依此类推)
还是我必须创建自己的并行类型类型层次结构(出于假定的法律原因而不必复制List<String>[]
)? (有图书馆吗?)
编辑:对于我为什么需要它有一些疑问。确实,为什么要完全查看泛型类型信息?
我从一个非通用的类/接口类型开始。 (如果要使用参数化类型,例如Type
,则始终可以使用新类添加间接层。)然后,我将关注字段或方法。这些可以引用参数化类型。只要他们不使用通配符,面对List<String>
之类的东西时,我仍然可以算出实际的静态类型。
通过这种方式,我可以使用高质量的静态输入来完成所有工作。这些T
动态类型都不在眼前。
我的具体用法是序列化。但这可能适用于反射的其他任何合理使用,例如测试。
我用于以下替换的代码的当前状态。 instanceof
是typeMap
。显示为“原样”快照。根本没有整理(Map<String,Type>
,如果您不相信我)。
throw null;
答案 0 :(得分:5)
十年来,我一直在以不满意的方式解决这个问题。首先使用Guice’s MoreTypes.java
,复制粘贴并使用Gson’s GsonTypes.java
进行修订,然后使用Moshi’s Util.java
。
莫希(Moshi)是我最好的方法,并不是说这很好。
您不能在Type的任意实现上调用equals()
并期望它起作用。
这是因为Java Types API提供了多种不兼容的方式来对简单类的数组进行建模。您可以将Date[]
作为Class<Date[]>
或将其组件类型为GenericArrayType
的{{1}}。我相信您会从类型为Date
的字段的反射中获得前者的,而从类型为Date[]
的字段的反射中获得的是后者。
未指定哈希码。
我还致力于实现Android使用的这些类的实现。 Android的早期版本具有与Java不同的哈希码,但是今天在野外发现的所有东西都使用与Java相同的哈希码。
toString方法不好
如果您在错误消息中使用类型,则必须编写特殊代码才能很好地打印它们,这很糟糕。
复制粘贴并感到悲伤
我的建议是不要对未知的Type实现使用equals()+ hashCode()。使用规范化功能可以转换为特定的已知实现,并且只能在您控制的实现范围内进行比较。
答案 1 :(得分:4)
这是一个直接依赖于Sun API和反射的小实验(也就是说,它使用反射与实现反射的类一起工作):
import java.lang.Class;
import java.lang.reflect.*;
import java.util.Arrays;
import sun.reflect.generics.reflectiveObjects.*;
class Types {
private static Constructor<ParameterizedTypeImpl> PARAMETERIZED_TYPE_CONS =
((Constructor<ParameterizedTypeImpl>)
ParameterizedTypeImpl
.class
.getDeclaredConstructors()
[0]
);
static {
PARAMETERIZED_TYPE_CONS.setAccessible(true);
}
/**
* Helper method for invocation of the
*`ParameterizedTypeImpl` constructor.
*/
public static ParameterizedType parameterizedType(
Class<?> raw,
Type[] paramTypes,
Type owner
) {
try {
return PARAMETERIZED_TYPE_CONS.newInstance(raw, paramTypes, owner);
} catch (Exception e) {
throw new Error("TODO: better error handling", e);
}
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
public static Type substituteTypeVariable(
final Type inType,
final TypeVariable<?> variable,
final Type replaceBy
) {
if (inType instanceof TypeVariable<?>) {
return replaceBy;
} else if (inType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) inType;
return parameterizedType(
((Class<?>) pt.getRawType()),
Arrays.stream(pt.getActualTypeArguments())
.map((Type x) -> substituteTypeVariable(x, variable, replaceBy))
.toArray(Type[]::new),
pt.getOwnerType()
);
} else {
throw new Error("TODO: all other cases");
}
}
// example
public static void main(String[] args) throws InstantiationException {
// type in which we will replace a variable is `List<E>`
Type t =
java.util.LinkedList
.class
.getGenericInterfaces()
[0];
// this is the variable `E` (hopefully, stability not guaranteed)
TypeVariable<?> v =
((Class<?>)
((ParameterizedType) t)
.getRawType()
)
.getTypeParameters()
[0];
// This should become `List<String>`
Type s = substituteTypeVariable(t, v, String.class);
System.out.println("before: " + t);
System.out.println("after: " + s);
}
}
在E
中用String
替换List<E>
的结果如下:
before: java.util.List<E>
after: java.util.List<java.lang.String>
主要思想如下:
sun.reflect.generics.reflectiveObjects.XyzImpl
类accessible
.newInstance
调用substituteTypeVariable
的简单递归方法中使用辅助方法,该方法使用类型变量替换为具体类型来重建Type
表达式。我并没有实现每种情况,但是它也应该适用于更复杂的嵌套类型(由于substituteTypeVariable
的递归调用)。
编译器不太喜欢这种方法,它会生成有关内部Sun API使用情况的警告:
警告:ParameterizedTypeImpl是内部专有API,在以后的版本中可能会删除
但是有一个@SuppressWarnings
for that。
上面的Java代码是通过翻译以下Scala小片段而获得的(这就是Java代码可能看起来有些奇怪而不是完全Java惯用的原因):
object Types {
import scala.language.existentials // suppress warnings
import java.lang.Class
import java.lang.reflect.{Array => _, _}
import sun.reflect.generics.reflectiveObjects._
private val ParameterizedTypeCons =
classOf[ParameterizedTypeImpl]
.getDeclaredConstructors
.head
.asInstanceOf[Constructor[ParameterizedTypeImpl]]
ParameterizedTypeCons.setAccessible(true)
/** Helper method for invocation of the `ParameterizedTypeImpl` constructor. */
def parameterizedType(raw: Class[_], paramTypes: Array[Type], owner: Type)
: ParameterizedType = {
ParameterizedTypeCons.newInstance(raw, paramTypes, owner)
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
def substituteTypeVariable(
inType: Type,
variable: TypeVariable[_],
replaceBy: Type
): Type = {
inType match {
case v: TypeVariable[_] => replaceBy
case pt: ParameterizedType => parameterizedType(
pt.getRawType.asInstanceOf[Class[_]],
pt.getActualTypeArguments.map(substituteTypeVariable(_, variable, replaceBy)),
pt.getOwnerType
)
case sthElse => throw new NotImplementedError()
}
}
// example
def main(args: Array[String]): Unit = {
// type in which we will replace a variable is `List<E>`
val t =
classOf[java.util.LinkedList[_]]
.getGenericInterfaces
.head
// this is the variable `E` (hopefully, stability not guaranteed)
val v =
t
.asInstanceOf[ParameterizedType]
.getRawType
.asInstanceOf[Class[_]] // should be `List<E>` with parameter
.getTypeParameters
.head // should be `E`
// This should become `List<String>`
val s = substituteTypeVariable(t, v, classOf[String])
println("before: " + t)
println("after: " + s)
}
}