在我当前的项目中,我有如下所示的类。在某些时候,像 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"
}
}
的A
类中有多个getId
方法。您将获得协变返回类型(未反映在虚拟机中的语言的"虚构"(的桥接方法。Class.getMethod
规范说它将返回具有最具体返回类型(假设存在(的方法。它对A
这样做,但对于B
,该方法不会被覆盖,因此javac避免了合成不必要的桥接方法。
实际上,对于此示例,所有信息仍存在于类文件中。 (早些时候我说过它没有被删除。这不是真的,但擦除并不意味着它不存在!然而,一般信息提取起来有点棘手(它将在Identifiable.class.getGenericReturnType()
、Identifiable.class.getTypeParameters()
、BaseEntity.class.getGenericInterfaces
、BaseEntity.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
而且,瞧,我们收到了观察到的结果。