Java: Instanceof and Generics
Asked Answered
W

9

178

Before I look through my generic data structure for a value's index, I'd like to see if it is even an instance of the type this has been parametrized to.

But Eclipse complains when I do this:

@Override
public int indexOf(Object arg0) {
    if (!(arg0 instanceof E)) {
        return -1;
    }

This is the error message:

Cannot perform instanceof check against type parameter E. Use instead its erasure Object since generic type information will be erased at runtime

What is the better way to do it?

Wince answered 15/10, 2009 at 3:0 Comment(0)
G
94

The error message says it all. At runtime, the type is gone, there is no way to check for it.

You could catch it by making a factory for your object like this:

public static <T> MyObject<T> createMyObject(Class<T> type) {
    return new MyObject<T>(type);
}

And then in the object's constructor store that type, so variable so that your method could look like this:

if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass())) {
    return -1;
}
Gambell answered 15/10, 2009 at 3:10 Comment(3)
@luis, Tom's comment probably meant I should use isInstance() and pass the actual arg0 parameter. It has the advantage of avoiding the null check.Gambell
@RubensMariuzzo maybe this helps: ralfebert.de/blog/java/isassignablefrom the important part here is: isAssignableFrom tells you if the generic type T is a super class of the instance arg0.Milwaukee
@Emerald214, you do not need to extend the class, but the class does have the static method (this is the static factory pattern javaworld.com/javaworld/jw-06-2001/j1-01-sintes2.html)Gambell
I
51

Two options for runtime type checking with generics:

Option 1 - Corrupt your constructor

Let's assume you are overriding indexOf(...), and you want to check the type just for performance, to save yourself iterating the entire collection.

Make a filthy constructor like this:

public MyCollection<T>(Class<T> t) {

    this.t = t;
}

Then you can use isAssignableFrom to check the type.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Each time you instantiate your object you would have to repeat yourself:

new MyCollection<Apples>(Apples.class);

You might decide it isn't worth it. In the implementation of ArrayList.indexOf(...), they do not check that the type matches.

Option 2 - Let it fail

If you need to use an abstract method that requires your unknown type, then all you really want is for the compiler to stop crying about instanceof. If you have a method like this:

protected abstract void abstractMethod(T element);

You can use it like this:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

You are casting the object to T (your generic type), just to fool the compiler. Your cast does nothing at runtime, but you will still get a ClassCastException when you try to pass the wrong type of object into your abstract method.

NOTE 1: If you are doing additional unchecked casts in your abstract method, your ClassCastExceptions will get caught here. That could be good or bad, so think it through.

NOTE 2: You get a free null check when you use instanceof. Since you can't use it, you may need to check for null with your bare hands.

Itching answered 12/6, 2013 at 17:53 Comment(1)
If T = Integer, o = Double, then there would be no Exception.Cord
P
24

Old post, but a simple way to do generic instanceOf checking.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}
Pontus answered 18/2, 2015 at 10:4 Comment(2)
It's unclear what you're actually contributing here.Ethban
Usable in special casesApache
S
16

Provided your class extends a class with a generic parameter, you can also get this at runtime via reflection, and then use that for comparison, i.e.

class YourClass extends SomeOtherClass<String>
{

   private Class<?> clazz;

   public Class<?> getParameterizedClass()
   {
      if(clazz == null)
      {
         ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
          clazz = (Class<?>)pt.getActualTypeArguments()[0];
       }
       return clazz;
    }
}

In the case above, at runtime you will get String.class from getParameterizedClass(), and it caches so you don't get any reflection overhead upon multiple checks. Note that you can get the other parameterized types by index from the ParameterizedType.getActualTypeArguments() method.

Subglacial answered 9/7, 2011 at 0:10 Comment(0)
U
7

I had the same problem and here is my solution (very humble, @george: this time compiling AND working ...).

My probem was inside an abstract class that implements Observer. The Observable fires method update(...) with Object class that can be any kind of Object.

I only want to handler Objects of type T

The solution is to pass the class to the constructor in order to be able to compare types at runtime.

public abstract class AbstractOne<T> implements Observer {

  private Class<T> tClass;
    public AbstractOne(Class<T> clazz) {
    tClass = clazz;
  }

  @Override
  public void update(Observable o, Object arg) {
    if (tClass.isInstance(arg)) {
      // Here I am, arg has the type T
      foo((T) arg);
    }
  }

  public abstract foo(T t);

}

For the implementation we just have to pass the Class to the constructor

public class OneImpl extends AbstractOne<Rule> {
  public OneImpl() {
    super(Rule.class);
  }

  @Override
  public void foo(Rule t){
  }
}
Unchancy answered 15/11, 2014 at 11:41 Comment(0)
V
6

Or you could catch a failed attempt to cast into E eg.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}
Vendue answered 28/10, 2012 at 5:29 Comment(2)
+1 A pragmatic non-over engineered solution for a simple check! I wish I could up voted this moreColumbous
This wouldn't work. E is erased at runtime, so the cast won't fail, you will just get a compiler warning about it.Gambell
G
1

Technically you shouldn't have to, that's the point of generics, so you can do compile-type checking:

public int indexOf(E arg0) {
   ...
}

but then the @Override may be a problem if you have a class hierarchy. Otherwise see Yishai's answer.

Grave answered 15/10, 2009 at 3:13 Comment(6)
yeah, the List interface demands that the function take an object parameter.Wince
you're implementing List? Why aren't you implementing List<E> ?Grave
(see for example the declaration of ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html)Grave
@Rosarch: the List interface's indexOf() method does not require that the argument given is the same type as the object you are looking for. it just has to be .equals() to it, and objects of different types can be .equals() to each other. This is the same issue as for the remove() method, see: #105299Mascia
[[[sheepishly]]] never mind, I thought indexOf() required E as a parameter rather than Object. (why did they do that?!??!)Grave
@Jason basically because it would break perfectly valid code that didn't require an potentially unsafe type cast. Also, because Generics are not covariant you can get into some edge cases with type parameters (say a list of lists that itself has a type parameter) that the compiler would block even though the indexOf() would actually return a real index (the object is in the list but the compiler can't let you see it).Gambell
D
1

The runtime type of the object is a relatively arbitrary condition to filter on. I suggest keeping such muckiness away from your collection. This is simply achieved by having your collection delegate to a filter passed in a construction.

public interface FilterObject {
     boolean isAllowed(Object obj);
}

public class FilterOptimizedList<E> implements List<E> {
     private final FilterObject filter;
     ...
     public FilterOptimizedList(FilterObject filter) {
         if (filter == null) {
             throw NullPointerException();
         }
         this.filter = filter;
     }
     ...
     public int indexOf(Object obj) {
         if (!filter.isAllows(obj)) {
              return -1;
         }
         ...
     }
     ...
}

     final List<String> longStrs = new FilterOptimizedList<String>(
         new FilterObject() { public boolean isAllowed(Object obj) {
             if (obj == null) {
                 return true;
             } else if (obj instanceof String) {
                 String str = (String)str;
                 return str.length() > = 4;
             } else {
                 return false;
             }
         }}
     );
Dimissory answered 15/10, 2009 at 4:8 Comment(1)
(Although you might want to do an instance type check if you are using Comparator or similar.)Dimissory
A
1

Let Java determine it and catch the exception bottom line.

public class Behaviour<T> {
    public void behave(Object object) {
        T typedObject = null;
        
        try { typedObject = (T) object; }
        catch (ClassCastException ignored) {}
        
        if (null != typedObject) {
            // Do something type-safe with typedObject
        }
    }
}
Ampliate answered 17/3, 2021 at 19:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.