hel*_*922 4 java generics type-erasure
Java Collections接口(例如,List
或Set
)定义contains
接受任何Object 的方法.
public boolean contains(Object o)
Run Code Online (Sandbox Code Playgroud)
然而,当涉及到实现这个方法时,我正在处理的特定集合要求我有一个与类的泛型类型兼容的类型E
(即要么是类E,要么是E的子类,或者是类如果E是接口,它实现E).换句话说,如果o可以转换为E类型,那么它是兼容的.这提出了一个问题,因为Java会删除Generic类型信息,所以这样的事情是不可能的:
public boolean contains(Object o)
{
if(o instanceof E) // compile error due to type erasures
{
// ... check if this collection contains o
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
我的问题是什么是最好的方法来完成这样的事情?关于Type Erasures的Oracle文章提到这是禁止的,但没有提供任何解决方案.
我只能想到一种半优雅的方式来解决这个问题:
进行强制转换以输入E.如果强制转换失败,则o不能是E类型或E类型的子类.
public boolean contains(Object o)
{
try
{
E key = (E) o; // I know it's unsafe, but if o is castable to E then the method should work
// check if this collection contains key
}
catch(ClassCastException e)
{
// invalid type, cannot contain o
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
虽然这可行,但看起来很混乱(我不是以这种方式使用Exceptions的忠实粉丝).
有没有更好的方法来实现这个目标(不允许更改方法签名)?
编辑:是的,这不起作用,因为E被删除到对象:(
这只能通过(1)传递预期Class
,或(2)检查定义的某些反射元素的泛型类型参数来实现<E>
.让我详细说明一下.
最常见的情况是简单地要求调用者传入运行时类E
.
public class MyClass<E> {
private final Class<E> realType;
public MyClass(Class<E> realType) {
this.realType = realType;
}
public boolean Contains(Object o) {
E e = realType.cast(o); // runtime cast - will throw ClassCastException.
// Could also use realType.isInstance(o)
// or realType.isAssignableFrom(o.getClass())
...
}
}
Run Code Online (Sandbox Code Playgroud)
呼叫者:
new MyClass<MyObject>(MyObject.class)
Run Code Online (Sandbox Code Playgroud)
这通常是类型安全的,因为编译器将验证<E>
匹配.当然调用者可以绕过编译器的检查......你无能为力!
对于(2),我的意思是你可以使用反射来检查静态泛型类型参数.在您的情况下,这可能不是一个好的选择,因为您必须能够访问静态定义的某些字段,方法或超类声明<E>
.最常见的方法是使您的类抽象化并让调用者扩展它.Hamcrest的TypeSafeMatcher(参见ReflectiveTypeFinder)使用这种方法效果很好.(请注意,TypeSafeMatcher基本上只是让程序员更容易使用选项(1);它仍然提供了一个构造函数,当反射不起作用时,它可以获取类的情况!)如果你想要真正看中,你可以检查getClass().getGenericSuperclass().getActualTypeArguments()
.这并不像听起来那么容易 - 看到这篇好文章.我甚至不确定该文章是否涵盖了所有边缘情况 - 您基本上是重新实现了编译器!所以只需选择(1)并开心你就不会在C#中使用泛型:-)