泛型继承和调用GetMethod().getReturnType()
在我当前的项目中,我的类的建模如下。在某些时候,类 A
和 B
上会调用类似 getReturnTypeForGetId()
的方法。使用 A
调用该方法会按预期返回 Integer
,但 B
返回 Serialized
。
我在这里缺少什么?我是否被一些令人发指的擦除行为所困扰,或者我只是错过了某种通用的上下文破坏?
编辑:向B
添加重写的getId()
方法可以解决问题,但我仍然想了解我遇到了什么。
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"
}
}
In my current project, I have classes which are modeled like the following. At some point, a method like getReturnTypeForGetId()
is called on classes A
and B
. Calling the method with A
returns Integer
as expected, but B
returns Serializable
.
What am I missing here? Am I getting bitten by some heinous erasure thing, or am I just missing out on some sort of generic context-clobbering?
EDIT: Adding an over-ridden getId()
method to B
fixes the problem, but I would still like to understand what I am running into.
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"
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
Java 允许所谓的“缩小”返回值类型。这就是您的示例完全有效的原因:
Serialized getId()
可以用任何可序列化返回类型覆盖,例如
Integer getId()
,如Integer
实现了Serialized
,因此在这种情况下允许缩小范围。因为
B
并没有重写getId()
,所以它的getId()
与从基本实体
。该声明在编译时被“类型擦除”
,瞧,我们收到了观察到的结果。
Java allows so called "narrowing" of return values' types. That's why your example works at all:
Serializable getId()
can be overridden with any serializable return type, like
Integer getId()
, asInteger
implementsSerializable
, so the narrowing is allowed in this case.Because
B
does not overridegetId()
itsgetId()
is the same as the one inherited fromBaseEntity
. The declarationis "type-erased" at compile time to
and, voilà, we receive the observed result.
编译后的
A
类中有多个getId
方法。您将获得协变返回类型的桥接方法(未反映在虚拟机中的语言的“虚构”)。 Class.getMethod 的规范表示它将返回具有最具体返回类型的方法(假设存在)。它对A
执行此操作,但对于B
该方法不会被重写,因此 javac 避免了合成不必要的桥接方法。事实上,对于这个示例,所有信息仍然存在于类文件中。 (之前我说过它没有删除这不是真的,但删除并不意味着它不存在!)然而,提取通用信息有点棘手(它将在
Identificate.class.getGenericReturnType()
、Identificate.class.getTypeParameters()
、BaseEntity.class.getGenericInterfaces
、BaseEntity.class。 getTypeParameters() 和 B.getGenericSuperclass (我认为!))。
使用 javap 来准确查看类文件中的内容。
There are multiple
getId
methods in the compiledA
class. You get a bridge method for the covariant return type (a "fiction" of the language not reflected in the virtual machine). The specification forClass.getMethod
says that it will return the method with the most specific return type (assuming that exists). It does this forA
, but forB
the method is not overridden so javac avoids synthesizing an unnecessary bridge method.In fact, for this example all the information is still there in the class files. (Earlier I said it wasn't erased. That's not true, but erasure doesn't mean that it isn't there!) The generic information is however a little tricky to extract (it'll be in
Identifiable.class.getGenericReturnType()
,Identifiable.class.getTypeParameters()
,BaseEntity.class.getGenericInterfaces
,BaseEntity.class.getTypeParameters()
andB.getGenericSuperclass
(I think!)).Use
javap
to see exactly what you have in the class files.在 A 类中,您重写 getId 以返回 Integer。
在类 B 中,您不会重写 getId,因此 B 中的 getId 方法是来自 BaseEntity 的方法。由于擦除,该返回可序列化。
In class A you override getId to return Integer.
In class B you don't override getId, so the getId method in B is the one from BaseEntity. Because of erasure, that one returns Serializable.
答案确实是类型擦除。请记住,泛型只是一种技巧,是未编译的 Java 代码中的提示。编译器删除与它们有关的所有内容以生成字节码。因此,当您在 getId 方法上使用反射时,您只能获得原始类型。
http://download.oracle.com/javase/tutorial/java/generics /erasure.html
但是,如果您询问此方法返回的实际对象的类 (B.getId),而不使用反射,由于其构造方式,您将得到一个 Integer。
The answer is indeed type erasure. Remember that generics are only a trick, hints in the non-compiled Java code. The compiler removes everything that has to do with them to produce bytecode. So when you use reflection on the getId method, you only get the raw type.
http://download.oracle.com/javase/tutorial/java/generics/erasure.html
But if you ask for the class of an actual object returned by this method (B.getId), without using reflection, due to the way it's constructed, you'll get an Integer.
BaseEntity 中的id 是私有并且“可序列化或扩展可序列化”。
B 类(扩展了 BaseEntity)对此字段一无所知。如果它定义了自己的 id 并且没有重写 getId()/setId(...) 这两个方法将继续使用 BaseEntity.id
如果您在 BaseEntity 中添加此方法:
它可以让您设置BaseEntity.id 为任何 Serialized。
在下面的测试中,您可以将 id 字段设置为例如 Float 值,并且所有内容都会编译并且未更改的 getId() 轻松返回 Float 值。
因此,如果您执行您所做的操作并询问“B.getId() 方法的返回类型是什么”,那么除非您重写 B 类中的 getId() 方法(这将强制它使用 Integer 函数类型并返回 Integer当然,请注意,那时 BaseEntity.id 甚至对 B 都不可见!)反射的答案不是 Integer,而是通用的 Serialized。因为任何可序列化的对象实际上都可能来自 getId() 方法。
id in BaseEntity is private and 'Serializable or extending Serializable'.
Class B (which extends BaseEntity) does not know anything about this field. If it defined its own id and did not override getId()/setId(...) those two methods would continue using the BaseEntity.id
If you add this method in the BaseEntity:
it lets you set the BaseEntity.id to any Serializable.
In following test you may then set the id field to e.g. a Float value and everything compiles and non-changed getId() comfortably returns the Float value.
Therefore, if you do what you do and ask 'What is the return type of B.getId() method', then unless you override getId() method in B class (which would force it to use the Integer function type and return Integer for sure. Note that BaseEntity.id would not even be visible to B then!) the reflection's answer is not Integer but a generic Serializable. Because any Serializable may come out of the getId() method really.