在我目前的项目中,我的课程模仿如下。在某些时候,类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"
}
}
答案 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.getGenericInterfaces
和BaseEntity.class.getTypeParameters()
(我认为!))。
使用B.getGenericSuperclass
查看您在类文件中的确切内容。
答案 2 :(得分:1)
答案确实是类型擦除。请记住,泛型只是一个技巧,在非编译的Java代码中提示。编译器删除与它们有关的所有内容以生成字节码。因此,当您在getId方法上使用反射时,您只能获得原始类型。
http://download.oracle.com/javase/tutorial/java/generics/erasure.html
但是如果你要求这个方法返回的实际对象的类(B.getId),而不使用反射,由于它的构造方式,你将得到一个整数。
答案 3 :(得分:1)
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}}
,瞧,我们收到了观察到的结果。