泛型继承和调用 GetMethod().getReturnType()



在我当前的项目中,我有如下所示的类。在某些时候,像 getReturnTypeForGetId() 这样的方法会在类 AB 上调用。使用 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"
    }
}
编译

A类中有多个getId方法。您将获得协变返回类型(未反映在虚拟机中的语言的"虚构"(的桥接方法。Class.getMethod规范说它将返回具有最具体返回类型(假设存在(的方法。它对A这样做,但对于B,该方法不会被覆盖,因此javac避免了合成不必要的桥接方法。

实际上,对于此示例,所有信息仍存在于类文件中。 (早些时候我说过它没有被删除。这不是真的,但擦除并不意味着它不存在!然而,一般信息提取起来有点棘手(它将在Identifiable.class.getGenericReturnType()Identifiable.class.getTypeParameters()BaseEntity.class.getGenericInterfacesBaseEntity.class.getTypeParameters()B.getGenericSuperclass(我认为!

使用javap可以准确查看类文件中的内容。

在类 A 中,您覆盖 getId 以返回整数。

在类 B 中,您不会重写 getId,因此 B 中的 getId 方法是来自 BaseEntity 的方法。由于擦除,该返回可序列化。

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

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

但是,如果您请求此方法返回的实际对象的类 (B.getId(,而不使用反射,由于其构造方式,您将获得一个 Integer。

BaseEntity 中的 id私有的,并且是"可序列化或扩展可序列化"。

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

如果在基实体中添加此方法:

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

它允许您将 BaseEntity.id 设置为任何可序列化。

在下面的测试中,您可以将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 确定。请注意,BaseEntity.id 甚至对 B 来说是不可见的!反射的答案不是整数,而是通用的可序列化。因为任何可序列化的都可能真的来自getId((方法。

Java允许所谓的"缩小"返回值的类型。这就是为什么您的示例完全有效的原因:

Serializable getId()

可以用任何可序列化的返回类型重写,例如

Integer getId(),因为Integer实现了Serializable,所以在这种情况下允许缩小。

因为B不会覆盖getId()所以它的getId()与从BaseEntity继承的相同。宣言

class B extends BaseEntity<Integer>

在编译时被"类型擦除"

class B extends BaseEntity

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

相关内容

  • 没有找到相关文章

最新更新