鉴于以下代码框架,是否可以确定属性foo
实际上是String
类型?
public class TestIntrospection {
public static class SuperBean<T> {
private T foo;
public T getFoo() { return foo; }
public void setFoo(T foo) { this.foo = foo; }
}
public static class SubBean extends SuperBean<String> {
}
public static void main(String[] args) throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(SubBean.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor prop : propertyDescriptors) {
if ("foo".equals(prop.getName())) {
System.out.printf("%s of %s\n", prop.getName(), prop.getPropertyType());
Method readMethod = prop.getReadMethod();
Type returnType = prop.getReadMethod().getGenericReturnType();
if (returnType instanceof TypeVariable) {
TypeVariable t = (TypeVariable) returnType;
GenericDeclaration d = t.getGenericDeclaration();
System.out.println("TypeVariable : " + t.getName() + " " + t.getBounds()[0]);
}
}
}
}
}
实际输出是
类java.lang.Object的foo
TypeVariable:T类java.lang.Object
编辑:我应该提到我知道类型擦除,并且该方法实际上是在字节码级别返回一个Object。尽管如此,有关泛型类型的元数据在类文件中可用,并且可以通过反射查询,如示例代码中所示。这是另一个片段,显示SubBean实际上具有String类型的类型参数:
Type superClass = SubBean.class.getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) superClass;
System.out.println(pt.getActualTypeArguments()[0]);
输出:
class java.lang.String
问题仍然存在,我如何将这个实际类型参数与类型变量联系起来?如果我知道只有一个类型参数,这很简单,但我希望这个代码也适用于具有多个泛型类型参数的bean。
答案 0 :(得分:5)
只要对象的运行时类确定了type参数的值,就可以通过递归替换从Class.getGenericSuperClass()获得的实际类型参数来推断其实际值:
class Substitution extends HashMap<String, TypeExpr> {
Substitution(TypeVariable[] formals, TypeExpr[] actuals) {
for (int i = 0; i < actuals.length; i++) {
put(formals[i].getName(),actuals[i]);
}
}
}
abstract class TypeExpr {
abstract TypeExpr apply(Substitution s);
public abstract String toString();
static TypeExpr from(Type type) {
if (type instanceof TypeVariable) {
return new TypeVar((TypeVariable) type);
} else if (type instanceof Class) {
return new ClassType((Class) type);
} else if (type instanceof ParameterizedType) {
return new ClassType((ParameterizedType) type);
} else if (type instanceof GenericArrayType) {
return new ArrayType((GenericArrayType) type);
} else if (type instanceof WildcardType) {
return new WildcardTypeExpr((WildcardType) type);
}
throw new IllegalArgumentException(type.toString());
}
static TypeExpr[] from(Type[] types) {
TypeExpr[] t = new TypeExpr[types.length];
for (int i = 0; i < types.length; i++) {
t[i] = from(types[i]);
}
return t;
}
static TypeExpr[] apply(TypeExpr[] types, Substitution s) {
TypeExpr[] t = new TypeExpr[types.length];
for (int i = 0; i < types.length; i++) {
t[i] = types[i].apply(s);
}
return t;
}
static void append(StringBuilder sb, String sep, Object[] os) {
String s = "";
for (Object o : os) {
sb.append(s);
s = sep;
sb.append(o);
}
}
}
class TypeVar extends TypeExpr {
final String name;
public TypeVar(String name) {
this.name = name;
}
public TypeVar(TypeVariable var) {
name = var.getName();
}
@Override
public String toString() {
return name;
}
@Override
TypeExpr apply(Substitution s) {
TypeExpr e = s.get(name);
return e == null ? this : e;
}
}
class ClassType extends TypeExpr {
final Class clazz;
final TypeExpr[] arguments; // empty if the class is not generic
public ClassType(Class clazz, TypeExpr[] arguments) {
this.clazz = clazz;
this.arguments = arguments;
}
public ClassType(Class clazz) {
this.clazz = clazz;
arguments = from(clazz.getTypeParameters());
}
@Override
public String toString() {
String name = clazz.getSimpleName();
if (arguments.length == 0) {
return name;
}
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append("<");
append(sb, ", ", arguments);
sb.append(">");
return sb.toString();
}
public ClassType(ParameterizedType pt) {
clazz = (Class) pt.getRawType();
Type[] args = pt.getActualTypeArguments();
arguments = TypeExpr.from(args);
}
@Override
ClassType apply(Substitution s) {
return new ClassType(clazz, apply(arguments, s));
}
}
class ArrayType extends TypeExpr {
final TypeExpr componentType;
public ArrayType(TypeExpr componentType) {
this.componentType = componentType;
}
public ArrayType(GenericArrayType gat) {
this.componentType = TypeExpr.from(gat.getGenericComponentType());
}
@Override
public String toString() {
return componentType + "[]";
}
@Override
TypeExpr apply(Substitution s) {
return new ArrayType(componentType.apply(s));
}
}
class WildcardTypeExpr extends TypeExpr {
final TypeExpr[] lowerBounds;
final TypeExpr[] upperBounds;
public WildcardTypeExpr(TypeExpr[] lowerBounds, TypeExpr[] upperBounds) {
this.lowerBounds = lowerBounds;
this.upperBounds = upperBounds;
}
WildcardTypeExpr(WildcardType wct) {
lowerBounds = from(wct.getLowerBounds());
upperBounds = from(wct.getUpperBounds());
}
@Override
TypeExpr apply(Substitution s) {
return new WildcardTypeExpr(
apply(lowerBounds, s),
apply(upperBounds, s)
);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("?");
if (lowerBounds.length > 0) {
sb.append(" super ");
append(sb, " & ", lowerBounds);
}
if (upperBounds.length > 0) {
sb.append(" extends ");
append(sb, " & ", upperBounds);
}
return sb.toString();
}
}
public class Test {
/**
* @return {@code superClazz}, with the replaced type parameters it has for
* instances of {@code ct}, or {@code null}, if {@code superClazz}
* is not a super class or interface of {@code ct}
*/
static ClassType getSuperClassType(ClassType ct, Class superClazz) {
if (ct.clazz == superClazz) {
return ct;
}
Substitution sub = new Substitution(ct.clazz.getTypeParameters(), ct.arguments);
Type gsc = ct.clazz.getGenericSuperclass();
if (gsc != null) {
ClassType sct = (ClassType) TypeExpr.from(gsc);
sct = sct.apply(sub);
ClassType result = getSuperClassType(sct, superClazz);
if (result != null) {
return result;
}
}
for (Type gi : ct.clazz.getGenericInterfaces()) {
ClassType st = (ClassType) TypeExpr.from(gi);
st = st.apply(sub);
ClassType result = getSuperClassType(st, superClazz);
if (result != null) {
return result;
}
}
return null;
}
public static ClassType getSuperClassType(Class clazz, Class superClazz) {
return getSuperClassType((ClassType) TypeExpr.from(clazz), superClazz);
}
测试代码:
public static void check(Class c, Class sc, String expected) {
String actual = getSuperClassType(c, sc).toString();
if (!actual.equals(expected)) {
throw new AssertionError(actual + " != " + expected);
}
}
public static void main(String[] args) {
check(Substitution.class, Map.class, "Map<String, TypeExpr>");
check(HashMap.class, Map.class, "Map<K, V>");
check(Bar.class, Foo.class, "Foo<List<? extends String[]>>");
}
}
interface Foo<X> {
}
class SuperBar<X, Y> implements Foo<List<? extends Y[]>> {
}
class Bar<X> extends SuperBar<X, String> { }
另一方面,如果类没有确定类型参数的值,则必须扩展bean以在运行时通过其他方式保留实际类型参数的类对象,例如:通过做:
class Super<T> {
final Class<T> clazz;
T foo;
Super(Class<T> clazz) {
this.clazz = clazz;
}
public T getFoo() {
return foo;
}
public T setFoo() {
this.foo = foo;
}
}
答案 1 :(得分:3)
我找到了一个解决方案,其中有一个单一超级的层次结构 class(除了Object),当超类上有多个类型参数时也可以工作。
仍然不适用于更深层次结构或实现泛型 接口。另外,我想确认这是事实记录的 应该工作。
public static class SuperBean<F, B, Q> {
// getters and setters
}
public static class SubBean<X> extends SuperBean<String, Integer, X> {
}
...
Type returnType = readMethod.getGenericReturnType();
Type superClass = SubBean.class.getGenericSuperclass();
GenericDeclaration genericDecl = ((TypeVariable) returnType).getGenericDeclaration();
TypeVariable[] parameters = genericDecl.getTypeParameters();
Type[] actualArgs = ((ParameterizedType) superClass).getActualTypeArguments();
for (int i=0; i<parameters.length; i++) {
//System.out.println(parameters[i] + " " + actualArgs[i]);
if (returnType == parameters[i]) {
System.out.println("Match : " + parameters[i] + " : " + actualArgs[i]);
}
}
输出:
类java.lang.Object的栏 匹配:B:类java.lang.Integer
类java.lang.Object的foo
匹配:F:类java.lang.String
类java.lang.Object的qux 匹配:问:X
我需要写一些更多的测试来确定如何处理后一种情况:)
答案 2 :(得分:2)
您可以通过this hack获取通用的运行时类型。从链接中提取的代码。
public class Base<T> {
private final Class<T> klazz;
@SuppressWarnings("unchecked")
public Base() {
Class<? extends Base> actualClassOfSubclass = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) actualClassOfSubclass.getGenericSuperclass();
Type firstTypeParameter = parameterizedType.getActualTypeArguments()[0];
this.klazz = (Class) firstTypeParameter;
}
public boolean accepts(Object obj) {
return this.klazz.isInstance(obj);
}
}
class ExtendsBase extends Base<String> {
// nothing else to do!
}
public class ExtendsBaseTest {
@Test
public void testTypeDiscovery() {
ExtendsBase eb = new ExtendsBase();
assertTrue(eb.accepts("Foo"));
assertFalse(eb.accepts(123));
}
}
答案 3 :(得分:1)
Java泛型在编译期间遇到类型擦除。在运行时,无法确定编译期间存在的T类型。
这是一个链接:type erasure
答案 4 :(得分:1)
不幸的是,类型擦除完全有效。
虽然看起来SubBean应该有一个固定类型的String用于那个ivar和那些方法,因为SuperBean
的类型参数在编译时是已知的,不幸的是,这不是它的工作方式。编译器在编译时不会为String
创建SuperBean
- {if}版本的SubBean
派生自 - 只有一个(类型已擦除)SuperBean
<击>
然而,我遇到的一个可能是丑陋的解决方法是SubBean
可能能够使用特定于类型的版本覆盖超类方法,然后BeanInfo可能会返回您对方法的期望:
public static class SubBean
extends SuperBean<String> {
// Unfortunate this is necessary for bean reflection ...
public String getFoo() { return super.getFoo(); }
public void setFoo(String foo) { super.setFoo(foo); }
}
击> <击> 撞击>
更新: 上述功能不起作用。请注意@JörnHorstmann在评论中发布的信息:
这似乎不起作用,因为Introspector仍然返回Object类型的read方法。此外,这似乎是一个生成的桥接方法(http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ102),这意味着如果我想访问此方法的注释,我可能会遇到bugs.sun.com/view_bug.do?bug_id=6788525。
上述解决方法的另一个丑陋变体是对属性进行别名:
public static class SubBean
extends SuperBean<String> {
// Unfortunate this is necessary for bean reflection ...
public String getFooItem() { return super.getFoo(); }
public void setFooItem(String foo) { super.setFoo(foo); }
}
SubBean
现在有一个独特的属性FooItem
,它是原始SuperBean
属性Foo
的别名。
答案 5 :(得分:0)
不幸的是,没有:
泛型是通过类型擦除实现的:泛型类型信息仅在编译时出现,之后由编译器擦除。这种方法的主要优点是它提供了通用代码和使用非参数化类型(在技术上称为原始类型)的遗留代码之间的完全互操作性。主要缺点是参数类型信息在运行时不可用,并且在与不良遗留代码进行互操作时自动生成的强制转换可能会失败。但是,有一种方法可以为通用集合实现有保证的运行时类型安全性,即使在与不良遗留代码进行互操作时也是如此。
引自http://download.oracle.com/javase/1.5.0/docs/guide/language/generics.html
答案 6 :(得分:0)
这是SuperBean
的字节代码:
public class foo.bar.SuperBean {
// Field descriptor #6 Ljava/lang/Object;
// Signature: TT;
private java.lang.Object foo;
// Method descriptor #10 ()V
// Stack: 1, Locals: 1
public SuperBean();
0 aload_0 [this]
1 invokespecial java.lang.Object() [12]
4 return
Line numbers:
[pc: 0, line: 3]
Local variable table:
[pc: 0, pc: 5] local: this index: 0 type: foo.bar.SuperBean
Local variable type table:
[pc: 0, pc: 5] local: this index: 0 type: foo.bar.SuperBean<T>
// Method descriptor #21 ()Ljava/lang/Object;
// Signature: ()TT;
// Stack: 1, Locals: 1
public java.lang.Object getFoo();
0 aload_0 [this]
1 getfield foo.bar.SuperBean.foo : java.lang.Object [24]
4 areturn
Line numbers:
[pc: 0, line: 8]
Local variable table:
[pc: 0, pc: 5] local: this index: 0 type: foo.bar.SuperBean
Local variable type table:
[pc: 0, pc: 5] local: this index: 0 type: foo.bar.SuperBean<T>
// Method descriptor #27 (Ljava/lang/Object;)V
// Signature: (TT;)V
// Stack: 2, Locals: 2
public void setFoo(java.lang.Object foo);
0 aload_0 [this]
1 aload_1 [foo]
2 putfield foo.bar.SuperBean.foo : java.lang.Object [24]
5 return
Line numbers:
[pc: 0, line: 12]
[pc: 5, line: 13]
Local variable table:
[pc: 0, pc: 6] local: this index: 0 type: foo.bar.SuperBean
[pc: 0, pc: 6] local: foo index: 1 type: java.lang.Object
Local variable type table:
[pc: 0, pc: 6] local: this index: 0 type: foo.bar.SuperBean<T>
[pc: 0, pc: 6] local: foo index: 1 type: T
}
如您所见,getter和setter都是java.lang.Object类型。 Introspector使用Getters和Setters生成PropertyDescriptor(字段被忽略),因此Property无法知道T的泛型类型。