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>
?
Use the IsGenericType property to determine whether the type is generic, and use the IsGenericTypeDefinition property to determine whether the type is a generic type definition. Get an array that contains the generic type arguments, using the GetGenericArguments method.
Only generic classes can implement generic interfaces. Normal classes can't implement generic interfaces.
The short answer is, that there is no way to find out the runtime type of generic type parameters in Java. I suggest reading the chapter about type erasure in the Java Tutorial for more details. A popular solution to this is to pass the Class of the type parameter into the constructor of the generic type, e.g.
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.
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 }
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 } } }
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. } }
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>
.
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.
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