Дженерики и 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.