Дженерики и java.зернышки.Интроспектор

учитывая следующий скелет кода, можно ли определить, что свойство 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 %sn", 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.ленг.Объект
TypeVariable: t класс java.ленг.Объект

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

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

выход:

класс java.ленг.Строка

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

7 ответов


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

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

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;
    }
}

Я нашел решение для случая, когда существует иерархия с одним супер класс (кроме объекта), который также работает, когда в супер классе есть несколько параметров типа.

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

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]);
                    }
                }

выход:

бар класса Ява.ленг.Объект
Match: B: класс java.ленг.Целое число
foo класса java.ленг.Объект
Матч: F: класс java.ленг.Строка
qux класса java.ленг.Объект
Матч: Q: X

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


вы можете получить тип среды выполнения generic с помощью этот хак . Код извлечен из ссылки.

    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 generics опыт стирания типа во время компиляции. Во время выполнения невозможно определить тип T, который присутствовал во время компиляции.

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


к сожалению, стирание типа в полной силе.

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

это, похоже, не работает, поскольку Интроспектор все еще возвращает метод чтения типа Object. Кроме того, это, по-видимому, сгенерированный метод моста (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.


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

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

Как цитируется из 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
}

Как вы можете видеть, как геттер и сеттер имеют тип Java.ленг.Объект. Интроспектор использует геттеры и сеттеры для создания PropertyDescriptor (поля игнорируются), поэтому свойство не может знать общий тип T.