Explain Codes LogoExplain Codes Logo

Get generic type of class at runtime

java
reflection
generics
type-erasure
Anton ShumikhinbyAnton Shumikhin·Sep 20, 2024
TLDR
// Stand back! We're going to try science! class Example<T> { Class<?> getGenericType() { return ((Class<?>) ((ParameterizedType) this.getClass() .getGenericSuperclass()).getActualTypeArguments()[0]); } } // Look, Ma! It works! class StringExample extends Example<String> {} System.out.println("Generic: " + new StringExample().getGenericType().getSimpleName()); // Output: String

Now, isn't that a beauty? The getGenericType() method is doing the heavy lifting here by getting hold of the elusive generic type at runtime. If you'll observe, it easily copped the String class as the generic type of StringExample. Clever, right?

The nitty-gritty of runtime type erasure

We all know that Java's notorious for type erasure at runtime. It's like that great magic trick where the rabbit disappears from the hat. Except, you actually need the rabbit during your act. So, we've got to find ways to keep the rabbit... I mean type in the hat!

Defeating Erasure with Reflection

Think of it as a game of Capture the Flag where the Flag is the Class<T> reference. We're going to capture it at construction time and keep it safe:

class GenericHolder<T> { private final Class<T> type; // Protected in our fortress! public GenericHolder(Class<T> type) { this.type = type; } Class<T> getGenericType() { return type; // Flag revealing time } } GenericHolder<String> holder = new GenericHolder<>(String.class); System.out.println("Generic type: " + holder.getGenericType().getSimpleName()); // String!

Guava's TypeToken to the Rescue

Here's another lifesaver: the TypeToken from Guava. You know how Batman has all those cool gadgets? Consider TypeToken as one of them:

TypeToken<String> typeToken = new TypeToken<String>() {}; System.out.println("Type: " + typeToken.getType());

Note: You need to use actual types when creating the TypeToken. Otherwise, the type information goes poof like the rabbit in the hat!

Delving deeper - Advanced Scenarios

Because life isn't always rainbows and unicorns, we've got some scenarios that are trickier than the usual:

Revealing the secrets of a generic field

If you're dealing with fields, you'll need to be more forensic, like so:

Field field = MyClass.class.getDeclaredField("myMap"); // Acting like Sherlock Holmes ParameterizedType mapGenericType = (ParameterizedType) field.getGenericType(); Type[] typeArgs = mapGenericType.getActualTypeArguments(); // Voila!

The Matryoshka dolls: Nested Generics

For situations when your generics are like those Russian Matryoshka dolls, nested and confusing, you've got to work your way through each layer:

Type typeOfMapValue = typeArgs[1]; if (typeOfMapValue instanceof ParameterizedType) { Type nestedType = ((ParameterizedType) typeOfMapValue).getRawType(); }

When generics decide to socialize: Generics on interfaces

When a class wants to be social and implements a generic interface, you can retrieve like this:

Type genericInterface = MyClass.class.getGenericInterfaces()[0]; // The crucio spell for interfaces!

Reflections and Pro Tip

Trade-offs are part of life (and Java)

Like every decision in life, Java's generic system involved trade-offs too, with the choice of encouraging backward compatibility. On the downside, we're left with no runtime generic type information. But hey, we've got solutions!

Generics in Action

Tools like Hibernate's Generic Data Access Objects to Guava's TypeToken are living examples of thriving despite the odds. They beautifully utilize ParameterizedType to make the most of complex scenarios.

Let's Agree to Disagree

Like most debates in life, Java generics is no exception. Some head over heels for its compile-time type safety, some not-so-much due to runtime type erasure. These patterns will help you handle both, all while maximizing type safety and functionality.