Java Language Use of instanceof with Generics


Example

Using generics to define the type in instanceof

Consider the following generic class Example declared with the formal parameter <T>:

class Example<T> {
    public boolean isTypeAString(String s) {
        return s instanceof T; // Compilation error, cannot use T as class type here
    }
}

This will always give a Compilation error because as soon as the compiler compiles the Java source into Java bytecode it applies a process known as type erasure, which converts all generic code into non-generic code, making impossible to distinguish among T types at runtime. The type used with instanceof has to be reifiable, which means that all information about the type has to be available at runtime, and this is usually not the case for generic types.

The following class represents what two different classes of Example, Example<String> and Example<Number>, look like after generics has stripped off by type erasure:

class Example { // formal parameter is gone
    public boolean isTypeAString(String s) {
        return s instanceof Object; // Both <String> and <Number> are now Object
    }
}

Since types are gone, it's not possible for the JVM to know which type is T.


Exception to the previous rule

You can always use unbounded wildcard (?) for specifying a type in the instanceof as follows:

    public boolean isAList(Object obj) {
        return obj instanceof List<?>;
    }

This can be useful to evaluate whether an instance obj is a List or not:

System.out.println(isAList("foo")); // prints false
System.out.println(isAList(new ArrayList<String>()); // prints true
System.out.println(isAList(new ArrayList<Float>()); // prints true

In fact, unbounded wildcard is considered a reifiable type.


Using a generic instance with instanceof

The other side of the coin is that using an instance t of T with instanceof is legal, as shown in the following example:

class Example<T> {
    public boolean isTypeAString(T t) {
        return t instanceof String; // No compilation error this time
    }
}

because after the type erasure the class will look like the following:

class Example { // formal parameter is gone
    public boolean isTypeAString(Object t) {
        return t instanceof String; // No compilation error this time
    }
}

Since, even if the type erasure happen anyway, now the JVM can distinguish among different types in memory, even if they use the same reference type (Object), as the following snippet shows:

Object obj1 = new String("foo"); // reference type Object, object type String
Object obj2 = new Integer(11); // reference type Object, object type Integer
System.out.println(obj1 instanceof String); // true
System.out.println(obj2 instanceof String); // false, it's an Integer, not a String