40

Possible Duplicate:
Generic type of local variable at runtime

I'm new to Java generics, and coming from a .NET world, I'm used to being able to write a method like this:

public void genericMethod<T>(T genericObject)
{
    if (genericObject is IList<String>)
    {
        //Do something...
    }            
}

The method accepts an object of a generic type, and checks whether that object implements a specific version of the generic interface IList<>, in this case, IList<String>.

Now, in Java, I'm able to do this:

public <T> void genericMethod(T genericObject)
{
    if (genericObject instanceof Set<?>)
    {
       //Do something...
    }
}

BUT

Java does not let me do if (genericObject instanceof Set<String>)

From what I know, because of type erasure, normally in Java this would be taken care of by a class object, and we would do something like the following:

public <T> void genericMethod(T genericObject)
{
    Class<OurTestingType> testClass = OurTestingType.class;
    if (genericObject.getClass() == testClass)
    {
       //Do something...
    }
}

but since the type I'm checking for is a generic interface, you can't do this:

Class<Set<String>> testClass = Set<String>.class

So, how, in Java, do I check if a generic object implements the specific type of Set<String>?

Community
  • 1
  • 1
Eric Cornelson
  • 751
  • 1
  • 7
  • 14
  • Generics are a compile time feature so you cannot simply get the specific generic type used at runtime. You could infer it from the contents or an additional argument e.g. `Class tClass` – Peter Lawrey Sep 20 '12 at 15:29

2 Answers2

29

Java implements erasure, so there's no way to tell on runtime if genericObject is an instance of Set<String> or not. The only way to guarantee this is to use bounds on your generics, or check all elements in the set.

Compile-time Generic Bounds

Using bounds checking, which will be checked at compile-time:

public <T extends SomeInterface> void genericMethod(Set<? extends T> tSet) {
    // Do something with tSet here
}

Java 8

We can use streams in Java 8 to do this natively in a single line:

public <T> void genericMethod(T t) {
    if (t instanceof Set<?>) {
        Set<?> set = (Set<?>) t;
        if (set.stream().allMatch(String.class:isInstance)) {
            Set<String> strs = (Set<String>) set;
            // Do something with strs here
        }
    }
}

Java 7 and older

With Java 7 and older, we need to use iteration and type checking:

public <T> void genericMethod(T t) {
    Set<String> strs = new HashSet<String>();
    Set<?> tAsSet;
    if (t instanceof Set<?>) {
        tAsSet = (Set<?>) t;
        for (Object obj : tAsSet) {
            if (obj instanceof String) {
                strs.add((String) obj);
            }
        }
        // Do something with strs here
    } else {
        // Throw an exception or log a warning or something.
    }
}

Guava

As per Mark Peters' comment below, Guava also has methods that do this for you if you can add it to your project:

public <T> void genericMethod(T t) {
    if (t instanceof Set<?>) {
        Set<?> set = (Set<?>) t;
        if (Iterables.all(set, Predicates.instanceOf(String.class))) {
            Set<String> strs = (Set<String>) set;
            // Do something with strs here
        }
    }
}

The statement, Iterables.all(set, Predicates.instanceOf(String.class)) is essentially the same thing as set instanceof Set<String>.

Brian
  • 17,079
  • 6
  • 43
  • 66
  • 3
    In Guava you can check if an `Iterable` contains only Strings using `Iterables.all(tAsSet, Predicates.instanceOf(String.class))`. Or filter using `Iterables.filter(tAsSet, String.class)`. – Mark Peters Sep 20 '12 at 16:48
  • @MarkPeters Guava always has neat little methods like that, good find. – Brian Sep 20 '12 at 16:59
  • It would be handy to have a method without instances... my case is a function `> funcname(T obj)`, and I'd like to do some checking on the K type. – Nyerguds Feb 05 '16 at 08:59
  • @Nyerguds To do that, just declare K first, like this: `>` – Brian Feb 11 '16 at 18:55
  • @Brian That still doesn't help me get the class of K for type checking or creating instances... – Nyerguds Feb 11 '16 at 19:00
  • @Nyerguds Then you'll need to pass in an instance of Class either via a constructor or the method that requires it. There is no other way to type check or create instances of a generic type because of erasure. – Brian Feb 12 '16 at 19:13
  • @Brian Yeah... I hate that about Java. All of that stuff works fine in C# :( – Nyerguds Feb 13 '16 at 20:22
  • 1
    Since Java 8 you don't need Guava to check if a `Set>` only contains Strings: `set.stream().allMatch((o) -> o instanceof String)` – farsil Aug 24 '18 at 08:52
  • 2
    @farsil: true! Even better: `set.stream().allMatch(String.class::isInstance)`. I'll update my answer. – Brian Aug 24 '18 at 15:57
  • Checking the content of a collection seems to be a non-sense to me. What if the collection is empty? – JeanValjean Jan 03 '20 at 14:43
  • @JeanValjean - the original question was geared around preventing runtime class cast exceptions when iterating over a collection. Since Java doesn't support `x instanceof List` and similar, then in this sense, an empty collection will never throw a class cast exception. Think of it as similar to `null`. In Java, `null` is an instance of nothing (`x instanceof Y` is always `false` if `x` is `null`) but `null` can be cast to anything (I can still do `z = (Y) x` if `x` is `null`). – Brian Jan 03 '20 at 22:12
11

You don't have that option in Java, sadly. In Java, there is no runtime difference between a List<String> and a List<Integer>. It is the compiler that ensures that you never add() an Integer to a List<String>. Even that compiler enforcement is not strict, so you can "legally" do such abominations with unchecked casts....

All in all, for (almost) any matter of runtime-type-identification, you have to take List<String> for what it actually is: just a raw List. That is called type erasure.

That said, nothing prevents you from inspecting the contents of a List for their types:

public boolean isListOf(List<?> list, Class<?> c) {
    for (Object o : list) {
        if (!c.isInstance(o)) return false;
    }

    return true;
}

To use this method:

    // ...
    if (genericObject instanceof List<?>) {
        if (isListOf((List<?>) genericObject, String.class)) {
            @SuppressWarnings("unchecked")
            List<String> strings = (List<String>) genericObject;
        }
    }

An interesting observation: if the list is empty, the method returns true for all given types. Actually there is no runtime difference between an empty List<String> and an empty List<Integer> whatsoever.

Saintali
  • 4,482
  • 2
  • 29
  • 49