繁体   English   中英

泛型和java.beans.Introspector

[英]Generics and java.beans.Introspector

给定以下代码框架,是否可以确定属性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。

只要对象的运行时类确定了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;
    }
}

我找到了一个解决方案,其中存在具有单个超类(除了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

我需要写一些更多的测试来确定如何处理最后一个案例:)

您可以通过此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));
  }
}

Java泛型在编译期间遇到类型擦除。 在运行时,无法确定编译期间存在的T的类型。

这是一个链接: 类型擦除

不幸的是,类型擦除完全有效。

虽然它看起来像SubBean应该为伊娃和固定String类型的那些方法,因为类型参数SuperBean是在编译时已知的,不幸的是,这不是它的工作方式。 编译器不创建一个String的-ified版本SuperBean在编译时间SubBean从派生-只有一个(类型擦除) SuperBean

然而,我 SubBean一个可能是丑陋的解决方法是, 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

抱歉不行:

泛型是通过类型擦除实现的:泛型类型信息仅在编译时出现,之后由编译器擦除。 这种方法的主要优点是它提供了通用代码和使用非参数化类型(在技术上称为原始类型)的遗留代码之间的完全互操作性。 主要缺点是参数类型信息在运行时不可用,并且在与不良遗留代码进行互操作时自动生成的强制转换可能会失败。 但是,有一种方法可以为通用集合实现有保证的运行时类型安全性,即使在与不良遗留代码进行互操作时也是如此。

引自http://download.oracle.com/javase/1.5.0/docs/guide/language/generics.html

这是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的泛型类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM