Дженерики и java.beans.Introspector - PullRequest
8 голосов
/ 02 февраля 2011

Учитывая следующий скелет кода, можно ли определить, что свойство 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]);
                }
            }
        }
    }
}

Фактический результат равен

foo класса java.lang.Object
TypeVariable: T class java.lang.Object

Редактировать: Я должен упомянуть, что я знаю об удалении типа и чтометод фактически возвращает объект на уровне байт-кода.Тем не менее, метаданные об общих типах доступны в файле классов и могут быть запрошены отражением, как в примере кода.Вот еще один фрагмент, который показывает, что SubBean на самом деле имеет параметр типа типа String:

                Type superClass = SubBean.class.getGenericSuperclass();
                ParameterizedType pt = (ParameterizedType) superClass;
                System.out.println(pt.getActualTypeArguments()[0]);

output:

class java.lang.String

Тогда остается вопрос, как связать этот фактический аргумент типа с переменной типа?Если я знаю, что есть только один параметр типа, это просто, но я хотел бы, чтобы этот код работал также для бинов, имеющих несколько параметров универсального типа.

Ответы [ 7 ]

5 голосов
/ 03 февраля 2011

Пока класс среды выполнения объекта определяет значение параметра типа, вы можете вывести его фактическое значение путем рекурсивной замены параметров формального типа на фактические, полученные из 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> { }

Если, с другой стороны, класс не определяет значение параметра типа, вам придется расширить свой компонент, чтобы сохранить объект класса для фактического параметра типа во время выполнения.другими способами, например, путем:

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;
    }
}
3 голосов
/ 03 февраля 2011

Я нашел решение для случая, когда существует иерархия с одним суперклассом (кроме 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
foo класса java.lang.Object
Соответствие: F: класс java.lang.String
qux классаjava.lang.Object
Match: Q: X

Мне нужно написать еще несколько тестов, чтобы определить, что делать с последним последним случаем:)

2 голосов
/ 02 февраля 2011

Вы можете получить универсальный тип во время выполнения с помощью 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));
  }
}
1 голос
/ 02 февраля 2011

К сожалению, стирание типов работает в полную силу.

Хотя кажется, что SubBean должен иметь фиксированный тип String для этого ivar и этих методов, потому что параметр типа для SuperBean известен при компиляции.Время, к сожалению, так не работает.Компилятор не создает String модифицированную версию SuperBean во время компиляции для 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örn Horstmann публикует в комментариях:

Это, похоже, не работает, поскольку Introspector все еще возвращает метод чтения типа Object.Кроме того, это похоже на сгенерированный метод моста (http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ102), что означает, что я могу столкнуться с bugs.sun.com / view_bug.do? Bug_id = 6788525 , если я хочу получить доступ к аннотациям на этомmethod.

Другим уродливым вариантом описанного выше обходного пути является псевдоним свойства:

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.

1 голос
/ 02 февраля 2011

Стирание типа опыта Java-обобщений во время компиляции. Во время выполнения невозможно определить тип T, который присутствовал во время компиляции.

Вот ссылка: Тип стирания

0 голосов
/ 02 февраля 2011

Вот байтовый код 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
}

Как видите, и метод получения, и метод установки имеют тип java.lang.Object.Introspector использует Getters и Setters для генерации PropertyDescriptor (поля игнорируются), поэтому свойство не может знать универсальный тип T.

0 голосов
/ 02 февраля 2011

К сожалению, нет:

Обобщения реализуются с помощью стирания типа: информация об универсальном типе присутствует только во время компиляции, после чего она стирается компилятором.Основным преимуществом этого подхода является то, что он обеспечивает полную совместимость между универсальным кодом и устаревшим кодом, который использует непараметрические типы (которые технически известны как необработанные типы).Основными недостатками является то, что информация о типе параметров недоступна во время выполнения, и что автоматически сгенерированные преобразования могут не работать при взаимодействии с унаследованным кодом с плохим поведением.Однако существует способ обеспечить гарантированную безопасность типов во время выполнения для универсальных коллекций, даже когда он взаимодействует с унаследованным кодом с плохим поведением.

Как указано http://download.oracle.com/javase/1.5.0/docs/guide/language/generics.html

...