通用继承和调用GetMethod()。getReturnType()

时间:2011-10-28 15:48:18

标签: java generics reflection

在我目前的项目中,我的课程模仿如下。在某些时候,类getReturnTypeForGetId()A上会调用类似B的方法。使用A调用方法会按预期返回Integer,但B会返回Serializable

我在这里缺少什么?我是否被一些令人发指的擦除事件所困扰,或者我只是错过了某种通用的上下文冲突?

编辑:向getId()添加一个过度使用的B方法可以解决问题,但我仍然想了解我遇到的问题。

import java.io.Serializable;

public class WeirdTester {
    static interface Identifiable<T extends Serializable> {
        T getId();
        void setId(final T id);
    }

    static abstract class BaseEntity<T extends Serializable> implements Identifiable<T> {
        private T id;
        public T getId() { return id; }
        public void setId(final T id) { this.id = id; }
    }

    static class A implements Identifiable<Integer> {
        private Integer id;
        public Integer getId() { return id; }
        public void setId(final Integer id) { this.id = id; }
    }

    static class B extends BaseEntity<Integer> {}

    @SuppressWarnings("unchecked")
    private static <T extends Serializable, Q extends Identifiable<T>> Class<T> getReturnTypeForGetId(
            final Class<Q> clazz) throws Exception {
        return (Class<T>) clazz.getMethod("getId", (Class[])null).getReturnType();
    }

    public static void main(final String[] args) throws Exception {
        System.out.println(getReturnTypeForGetId(A.class));
        // CONSOLE: "class java.lang.Integer"
        System.out.println(getReturnTypeForGetId(B.class));
        // CONSOLE: "interface java.io.Serializable"
    }
}

5 个答案:

答案 0 :(得分:2)

在A类中,您重写getId以返回Integer。

在B类中,您不会覆盖getId,因此B中的getId方法是BaseEntity中的方法。由于擦除,那个返回Serializable。

答案 1 :(得分:2)

编译的getId类中有多个A方法。您将获得协变返回类型的桥接方法(未在虚拟机中反映的语言的“虚构”)。 Class.getMethod的规范说它将返回具有最特定返回类型的方法(假设存在)。它为A执行此操作,但对于B,该方法不会被覆盖,因此javac避免合成不必要的桥接方法。

实际上,对于此示例,所有信息仍然存在于类文件中。 (之前我说它不是erased。这不是真的,但擦除并不意味着它不存在!)然而,通用信息提取起来有点棘手(它将在{{ 1}},Identifiable.class.getGenericReturnType()Identifiable.class.getTypeParameters()BaseEntity.class.getGenericInterfacesBaseEntity.class.getTypeParameters()(我认为!))。

使用B.getGenericSuperclass查看您在类文件中的确切内容。

答案 2 :(得分:1)

答案确实是类型擦除。请记住,泛型只是一个技巧,在非编译的Java代码中提示。编译器删除与它们有关的所有内容以生成字节码。因此,当您在getId方法上使用反射时,您只能获得原始类型。

http://download.oracle.com/javase/tutorial/java/generics/erasure.html

但是如果你要求这个方法返回的实际对象的类(B.getId),而不使用反射,由于它的构造方式,你将得到一个整数。

答案 3 :(得分:1)

BaseEntity中的

id 私有和'可序列化或扩展可序列化'。

B类(扩展BaseEntity)对此字段一无所知。如果它定义了自己的 id 并且没有覆盖getId()/ setId(...),那么这两种方法将继续使用BaseEntity.id

如果您在BaseEntity中添加此方法:

public void setId2(final Serializable id) {
        this.id = (T) id;
}

它允许您将BaseEntity.id设置为任何Serializable。

在以下测试中,您可以将id字段设置为例如一个 Float 值,所有编译和未更改的 getId()都可以轻松返回Float值。

B b = new B();
b.setId2(2.1F);
System.out.println( b.getId() ); //prints out 2.1

因此,如果您执行的操作并询问'B.getId()方法的返回类型是什么',那么除非您在B类中重写getId()方法(这会强制它使用Integer函数类型)并且肯定会返回Integer。注意,然后B对BaseEntity.id甚至不可见!)反射的答案不是Integer而是通用的Serializable。因为任何Serializable都可能真的来自getId()方法。

答案 4 :(得分:-1)

Java允许返回值类型的所谓“缩小”。这就是为什么你的例子可以工作的原因:

Serializable getId()

可以使用任何可序列化的返回类型覆盖

,例如

Integer getId()Integer实现Serializable,因此在这种情况下允许缩小范围。

由于B 覆盖getId()getId()BaseEntity继承的class B extends BaseEntity<Integer> 相同。声明

class B extends BaseEntity

在编译时被“类型擦除”到

{{1}}

,瞧,我们收到了观察到的结果。