检查通用类型

hel*_*922 4 java generics type-erasure

Java Collections接口(例如,ListSet)定义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 ErasuresOracle文章提到这是禁止的,但没有提供任何解决方案.

我只能想到一种半优雅的方式来解决这个问题:

进行强制转换以输入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被删除到对象:(

Pet*_*vis 5

这只能通过(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#中使用泛型:-)