2011-02-02 34 views
8

Với mã bộ xương sau, có thể xác định được rằng bất động sản foo thực tế là loại String?Generics and java.beans.Introspector

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

Sản lượng thực tế là

foo của lớp java.lang.Object
TypeVariable: T lớp java.lang.Object

Edit: tôi nên có mentionend mà tôi biết về loại tẩy xoá và rằng phương thức trên thực tế là trả về một đối tượng ở cấp độ bytecode. Tuy nhiên, siêu dữ liệu về các kiểu generic có sẵn trong tệp lớp và có thể được truy vấn bằng sự phản chiếu như trong mã mẫu. Dưới đây là một đoạn mã cho thấy rằng SubBean trên thực tế có một tham số loại kiểu String:

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

đầu ra:

lớp java.lang.String

Câu hỏi còn lại, làm cách nào để liên kết đối số loại thực tế này với biến kiểu? Nếu tôi biết rằng chỉ có một tham số kiểu này là đơn giản, nhưng tôi muốn mã này cũng hoạt động đối với các bean có nhiều tham số kiểu generic.

+1

Thư viện java ClassMate được mô tả tại http://www.cowtowncoder.com/blog/archives/2012/04/entry_471.html dường như hỗ trợ độc đáo cho việc giải quyết các loại chung chung. Dự án có sẵn tại github: https://github.com/cowtowncoder/java-classmate –

+0

Như của Java 1.7.0_80 đầu ra là 'foo của lớp java.lang.String TypeVariable: T class java.lang.Object' –

Trả lời

5

Chừng nào lớp thời gian chạy của đối tượng xác định giá trị của tham số kiểu , bạn có thể suy ra giá trị thực tế của nó bằng cách đệ quy thay thế các thông số loại chính thức của những người thực tế thu được từ Class.getGenericSuperClass(): mã

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

Test:

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

Nếu mặt khác lớp không xác định giá trị của tham số kiểu, bạn sẽ phải mở rộng bean để giữ đối tượng lớp cho tham số kiểu thực tại thời gian chạy bằng các phương tiện khác, ví dụ: bằng cách thực hiện:

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

Loại hình kinh nghiệm về loại Generics trong quá trình biên dịch Java. Khi chạy, không thể xác định loại T đã có trong quá trình biên dịch.

Dưới đây là một liên kết: type erasure

+1

Tôi e rằng đó là một trong những trường hợp mà Hướng dẫn Java không nói đầy đủ sự thật. Những gì họ có nghĩa là để nói là các thông số loại không tồn tại trong bytecode. Các loại được khai báo, ngay cả khi chúng là chung, có mặt trong tệp lớp và có thể được kiểm tra bằng cách sử dụng sự phản chiếu. Trong mẫu mã từ câu hỏi, thông tin đó đủ để xây dựng lại tham số kiểu khi chạy. – meriton

0

Thật không may, không có:

Generics được thực hiện theo loại tẩy xoá: loại thông tin chung chỉ hiện diện tại thời gian biên dịch, sau đó nó được xóa bởi trình biên dịch. Ưu điểm chính của phương pháp này là nó cung cấp khả năng tương tác tổng thể giữa mã chung và mã kế thừa sử dụng các kiểu không tham số (được gọi là kiểu thô). Những bất lợi chính là thông tin kiểu tham số không có sẵn trong thời gian chạy, và các phôi được tạo tự động có thể thất bại khi tương tác với mã kế thừa không được xử lý. Tuy nhiên, có một cách để đạt được sự an toàn kiểu thời gian chạy được bảo đảm cho các bộ sưu tập chung ngay cả khi tương tác với mã kế thừa không được xử lý.

Theo trích dẫn từ http://download.oracle.com/javase/1.5.0/docs/guide/language/generics.html

+2

Tôi e rằng đó là một trong những trường hợp mà Hướng dẫn Java không nói đầy đủ sự thật. Những gì họ có nghĩa là để nói là các thông số loại không tồn tại trong bytecode. Các loại được khai báo, ngay cả khi chúng là chung, có mặt trong tệp lớp và có thể được kiểm tra bằng cách sử dụng sự phản chiếu. Trong mẫu mã từ câu hỏi, thông tin đó đủ để xây dựng lại tham số kiểu khi chạy. – meriton

2

Bạn có thể nhận được loại thời gian chạy của chung bằng phương pháp this hack. Mã được trích xuất từ ​​liên kết.

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

Dưới đây là các mã byte của 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 
} 

Như bạn có thể thấy, cả hai phương thức getter và setter là loại java.lang.Object. Introspector sử dụng Getters and Setters để tạo ra PropertyDescriptor (các trường được bỏ qua), do đó không có cách nào mà Property có thể biết được kiểu generic của T.

+0

Đối tượng được kiểm tra là loại 'SubBean', không phải là' SuperBean'. Tệp lớp của 'SubBean' chứa mệnh đề mở rộng đầy đủ, cho thấy' T' thực sự là viết tắt của String cho các thể hiện của 'SubBean'. Thông tin này có thể truy cập được khi chạy. – meriton

+0

@meriton Đúng (Tôi đoán là tôi không xem xét kỹ câu hỏi này). Tuy nhiên, các phương thức được định nghĩa trong lớp cha và Introspector sử dụng các định nghĩa đó và bỏ qua thông tin kiểu chung của trẻ. –

1

Thật không may, loại tẩy xóa có hiệu lực hoàn toàn. Mặc dù nó có vẻ như SubBean nên có một loại cố định của String cho ivar đó và những phương thức đó vì tham số kiểu cho SuperBean được biết đến tại thời gian biên dịch, thật không may, đó không phải là cách nó hoạt động. Trình biên dịch không tạo ra một phiên bản -ified String của SuperBean tại thời gian biên dịch cho SubBean để lấy được từ - chỉ có một (type-xóa) SuperBean

Một workaround có thể xấu xí đó xảy ra với tôi tuy nhiên là SubBean có thể ghi đè lên các phương pháp lớp cha với phiên bản loại cụ thể và sau đó BeanInfo có thể trở lại những gì bạn mong đợi cho các phương pháp:

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

Cập nhật: Ở trên không có tác dụng. Lưu ý thông tin này mà @ Jörn Horstmann đăng trong các nhận xét:

Điều này dường như không hoạt động vì Introspector vẫn trả về một phương thức đọc đối tượng kiểu. Ngoài ra, đây có vẻ là phương thức cầu được tạo (http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ102) có nghĩa là tôi có thể chạy vào bugs.sun.com/view_bug.do?bug_id=6788525 nếu tôi muốn truy cập chú thích trên phương pháp này.

Một biến thể xấu xí của workaround trên là bí danh tài sản:

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 bây giờ có một tài sản riêng biệt FooItem đó là một bí danh cho ban SuperBean tài sản Foo.

+1

Điều này dường như không hoạt động vì Introspector vẫn trả về một phương thức đọc kiểu Object. Ngoài ra, đây có vẻ là phương thức cầu được tạo (http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ102) có nghĩa là tôi có thể chạy vào http://bugs.sun.com/view_bug.do ? bug_id = 6788525 nếu tôi muốn truy cập chú thích trên phương pháp này. –

+0

@ Jörn Horstmann - Cảm ơn bạn đã cập nhật. Tôi xin lỗi nó đã không làm việc cho bạn - nó có vẻ như nó nên, nhưng đưa ra thông tin về trình biên dịch tạo ra các phương pháp cầu, tôi có thể thấy sự nhầm lẫn. Đề xuất khác duy nhất tôi có thể nghĩ là triển khai thuộc tính bí danh, ví dụ: 'public String getFooItem() {return super.getFoo(); } 'cho' SubBean' một thuộc tính rõ ràng 'FooItem' thực sự là một bí danh cho' Foo'. –

+1

Cảm ơn, việc ghi đè các phương pháp chắc chắn đáng để thử và giúp hiểu một số khía cạnh khác của việc thực hiện Generics. Nhưng mục tiêu của tôi thực sự là sử dụng điều này trong một thư viện để xem xét các bean tùy ý mà không phải thay đổi chúng trước. –

3

Tôi đã tìm thấy giải pháp cho trường hợp có hệ thống phân cấp với một lớp siêu đơn (ngoài Object) cũng hoạt động khi có nhiều thông số loại trên lớp siêu.

Vẫn không hoạt động đối với các phân cấp sâu hơn hoặc khi triển khai giao diện chung . Ngoài ra tôi muốn có một xác nhận rằng đây là trong thực tế tài liệu và nghĩa vụ phải làm việc.

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

Output:

thanh của lớp java.lang.Object
trận đấu: B: lớp java.lang.Integer
foo của lớp java.lang.Object
Trùng khớp: F: class java.lang.String
qux của lớp java.lang.Object
trận đấu: Q: X

tôi cần phải viết một số xét nghiệm nữa để xác định những gì để làm với cuối trường hợp cuối cùng :)