2011-07-09 147 views
3

Java集合接口(例如,ListSet)定义了接受任何对象的contains方法。检查泛型类型

public boolean contains(Object o) 

然而,当涉及到实施这种方法,我工作特别的收藏需要,我有一种类型是与类的泛型类型E(即或者是E级,或兼容E的子类,或者如果E是接口,则实现E的类)。换句话说,如果o可铸造成E型,那么它是兼容的。这就提出一个问题,因为Java擦除泛型类型的信息,所以这样的事情是不可能的:

public boolean contains(Object o) 
{ 
    if(o instanceof E) // compile error due to type erasures 
    { 
     // ... check if this collection contains o 
    } 
    return false; 
} 

我的问题是什么是完成这样的事情的最好方法? Oracle Article on Type Erasures提到这是禁止的,但不提供任何解决此问题的方法。

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

做强制转换为类型E.如果转换失败,邻类型不能为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; 
} 

虽然这会工作,但看起来很乱(我不喜欢以这种方式使用例外)。

有没有更好的方法来实现这个相同的目标(改变方法签名是不允许的)?

编辑:是的,这不起作用由于E被擦除对象:(

+3

它工作吗?你会得到一个未经检查的铸造编译器警告。你永远不会得到一个ClassCastException(除非'o'不是'E'的**擦除**的实例,可能是'Object')。 –

+0

您能否详细说明“要求我有一个与类的泛型E兼容的类型?”谢谢。 –

+0

我同意 - 这是行不通的。你确实需要实际的类做适当的演员,可能会失败。 – StaxMan

回答

4

这是唯一可行的:(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()) 
     ... 
    } 
} 

来电:。

new MyClass<MyObject>(MyObject.class) 

这通常是类型安全的,因为编译器将验证<E>的比赛。当然,调用者可以绕过编译器的检查...你无能为力!对于(2),我的意思是你可以使用反射来检查静态泛型类型参数。在你的情况下,这可能不是一个好的选择,因为你必须有权访问某些静态定义为<E>的字段,方法或超类声明。最常见的方法是让你的课程抽象并让调用者扩展它。 Hamcrest的TypeSafeMatcher(见ReflectiveTypeFinder)使用此方法,效果很好。 (请注意,TypeSafeMatcher基本上只是让选项(1)更容易为程序员;它仍然提供了一个构造函数,该类用于反射不起作用的情况!)如果你想变得很花哨,你可以检查getClass().getGenericSuperclass().getActualTypeArguments()。这并不像听起来那么容易 - 见this good article。我甚至不确定那篇文章是否涵盖了所有的边界情况 - 你基本上是在重新编译编译器!所以只需要选择(1)并且很高兴你在C#中不使用泛型:-)

+0

如果你像这样手动设置类型,泛型的要点是什么? –

+0

泛型仍然通过消除大多数强制转换和改进编译器类型检查来清理源代码。不是捍卫Java使用类型擦除的决定,但.NET泛型也有缺点。 –

0

我不明白你为什么这样用.contains()搞乱。 Collection中的规范说,如果给定对象等于(在.equals()的意义上)容器的某个元素,它将返回true。那么你是否在改变.contains()的含义?你的平等是如何定义的?

+0

Java TreeSet类不使用equals()方法来定义相等性(而是使用compareTo()方法或比较器)。试图将不能转换的对象传递给TreeSet的泛型参数是一个ClassCastException(它是contains()方法的Javadoc的一部分)。我有类似的情况与不同类型的树状数据结构。有趣的是,Javadoc确实说它对contains()使用equals()方法。 – helloworld922

+0

@ helloworld922:“尝试将不能转换的对象传递给该TreeSet的泛型参数是一个ClassCastException”这不是事实。运行时不存在通用参数。所有TreeSet在运行时都会将对象转换为Comparable,然后使用“compareTo”将其与其他对象进行比较。只有当您输入的对象不能相互比较时才会出现ClassCastException。 – newacct

+0

啊,真的。我习惯于让Comparable对象通常与自己相媲美,但是你说得对,那并不需要是真实的。 – helloworld922