The Java Collections interfaces (for example, List
or Set
) define the contains
method to accept any Object.
public boolean contains(Object o)
However, when it comes to implementing this method, the particular collection I'm working on requires that I have a type which is compatible with the generic type E
of the class (i.e. either be class E, or a subclass of E, or a class which implements E if E is an interface). In other words, if o is castable to type E, then it is compatible. This presents an issue because Java erases the Generic type information, so something like this isn't possible:
public boolean contains(Object o)
{
if(o instanceof E) // compile error due to type erasures
{
// ... check if this collection contains o
}
return false;
}
My question is what would be the best way to accomplish something like this? The Oracle Article on Type Erasures mentions that this forbidden, but does not offer any solutions to this problem.
I can only think of one semi-elegant way to get around this:
Make a cast to type E. If the cast fails, o cannot be type E or a subclass of type 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;
}
While this would work, it looks messy (I'm not a big fan of using Exceptions in this manner).
Is there a better way to accomplish this same goal (changing the method signature is not allowed)?
edit: yeah, this doesn't work because E gets erased to Object :(
This is only possible by (1) passing in the expected Class
, or (2) examining the generic type parameters of some reflective element that defines <E>
. Let me elaborate.
The most common case is to simply require the caller to pass in the runtime class of 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())
...
}
}
Caller:
new MyClass<MyObject>(MyObject.class)
This is generally type safe since the compiler will verify that the <E>
's match. Of course the caller can bypass the compiler's checks...nothing you can do about that!
For (2), what I mean is that you can use reflection to examine static generic type parameters. This probably isn't a good option in your case because you must have access to some field, method, or superclass declaration that statically defines <E>
. The most common way is to make your class abstract and have callers extend it. This approach is used by Hamcrest's TypeSafeMatcher (see ReflectiveTypeFinder) to great effect. (Note that TypeSafeMatcher is basically just making option (1) easier for the programmer; it still provides a constructor that takes the Class for cases when reflection doesn't work!) If you want to get really fancy, you can inspect getClass().getGenericSuperclass().getActualTypeArguments()
. This isn't as easy as it sounds -- see this good article. I'm not even sure that article covers all the edge cases -- you're basically reimplementing the compiler! So just go with option (1) and be happy you're not using generics in C# :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With