octopod
octopod

Reputation: 874

Is retrieving the generic parameter of a class using the reflection method guaranteed to work (in my case)?

Alright, so I'm trying to design an event system for myself, and this is what I have so far for an event handler (Event is some empty class):

public interface EventHandler
{
    public Class<? extends Event> type();

    public void handle(Event event);
}

The goal is that an EventHandler can only handle one type of event (at this point). But, I wonder if I could reformat it like this:

public interface EventHandler <T extends Event>
{
    public void handle(T event);
}

This would help me in two ways:

Based on this question: Reflection for Class of generic parameter in Java?, I can get T by this method:

ParameterizedType t = (ParameterizedType) MyClass.class.getGenericSuperclass(); //OtherClass<String>
Class<?> clazz = (Class<?>) t.getActualTypeArguments()[0]; // Class<String>

I am aware that Type Erasure is a factor when dealing with stuff like this, but what I don't understand is when it would apply to my use of EventHandler<T>, or pretty much at all to be honest. For example, the only way I would use EventHandler<T> is:

public class TestEvent extends Event {}

public class TestEventHandler implements EventHandler<TestEvent>
{
    @Override
    public void handle(TestEvent event)
    {
        System.out.println("Event Handled");
    }
}

(my Events would be in their own package, and the handlers would be in another)

The soon-to-be event bus would register this handler by class and get the generic Event type of it to use as a key in a map of Event classes and EventHandlers.

Anyways, any clarification would be appreciated. If I could only get the generic type like half the time or something, it wouldn't work out, and I'd probably have to do a version without generic types. Feel free to criticize how EventHandler is formatted to begin with in your answer if you want.

Upvotes: 2

Views: 155

Answers (2)

newacct
newacct

Reputation: 122439

The code you showed relies on MyClass being a direct implementing class of EventHandler, and that MyClass implements EventHandler's type parameter with a concrete class as the type argument. So it must be something like this:

class MyClass implements EventHandler<String>

Most of the time where this is used (e.g. with type tokens), the user is expected to create an anonymous class which directly implements EventHandler<String> like this: new EventHandler<String>() { /* body of anonymous class, can be empty */ }, which is equivalent to the name type above except for the anonymous part.

Some scenarios where the code will not work:

  • If MyClass is generic and implements EventHandler with a type variable as the type argument instead of a concrete class:

    class MyClass<T> implements EventHandler<T>
    
  • If MyClass does not directly implement EventHandler, but goes through one or more non-generic classes in between:

    class MyClass implements YourClass
    class YourClass implements EventHandler<String>
    

(in this latter case, it would be possible for smart code to traverse the type hierarchy to find the argument with which EventHandler is implemented, but it would need to be very complicated to handle all the cases, e.g. YourClass could itself be generic, and use one of its parameters to implement EventHandler)

Upvotes: 0

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 279950

This reflection trick is also used for type tokens (look 'em up).

Here's an example of type erasure occurrence

public class Generic<TypeParameter> {
    public void method(TypeParameter parameter) {
        // no way to know the type bound to TypeParameter due to Type Erasure
    }
} 

But it doesn't apply to your case. A declaration like

public class TestEventHandler extends EventHandler<TestEvent>

is very explicit. It uses TestEvent as a type argument for the generic EventHandler type. What's more, the method declaration

@Override
public void handle(TestEvent event)
{
    System.out.println("Event Handled");
}

also tells us that the parameter is of type TestEvent. This information is not lost at runtime. It's part of the Class information (it's in the byte code).

If you keep using making your EventHandler types like your TestEventHandler, you'll be able to use the reflection trick to get the Event type. Note that you could also use anonymous subclasses

new EventHandler<TestEvent>() {/*body*/};

to achieve the same thing.

Upvotes: 1

Related Questions