52

I have the following interface, which I want to implement multiple times in my classes:

public interface EventListener<T extends Event>
{
    public void onEvent(T event);
}

Now, I want to be able to implement this interface in the following way:

class Foo implements EventListener<LoginEvent>, EventListener<LogoutEvent>
{

    @Override
    public void onEvent(LoginEvent event)
    {

    }

    @Override
    public void onEvent(LogoutEvent event)
    {

    }
}

However, this gives me the error: Duplicate class com.foo.EventListener on the line:

class Foo implements EventListener<LoginEvent>, EventListener<LogoutEvent>

Is it possible to implement the interface twice with different generics? If not, what's the next closest thing I can do to achieve what I'm trying to do here?

Ali
  • 261,656
  • 265
  • 575
  • 769
  • 1
    Derive a sub-interface of `EventListener`, and have this class implement that? (I don't know if it would work, I'm just tossing it out as a possible workaround.) – keshlam Mar 03 '14 at 04:08
  • 1
    @keshlam i still wouldn't be able to have two separate methods for handling login and logout events in that case. – Ali Mar 03 '14 at 04:10
  • 3
    look at http://stackoverflow.com/questions/1297972/how-to-make-a-java-class-that-implements-one-interface-with-two-generic-types – codingenious Mar 03 '14 at 04:10
  • 2
    JFYI You can achieve this in C# by the explicit interface implementation. – Kishan Vaishnav Dec 17 '18 at 07:07
  • check this functional style with compile time type checking example - https://stackoverflow.com/a/60466413/4121845 – mano_ksp Feb 29 '20 at 15:01

3 Answers3

30

Is it possible to implement the interface twice with different generics

Unfortunately no. The reason you can't implement the same interface twice is because of type erasure. The compiler will handle type parameters, and a runtime EventListener<X> is just a EventListener


If not, what's the next closest thing I can do to achieve what I'm trying to do here?

Type erasure can work in our favor. Once you know that EventListener<X> and EventListener<Y> are just raw EventListener at run-time, it is easier than you think to write an EventListener that can deal with different kinds of Events. Bellow is a solution that passes the IS-A test for EventListener and correctly handles both Login and Logout events by means of simple delegation:

@SuppressWarnings("rawtypes")
public class Foo implements EventListener {

    // Map delegation, but could be anything really
    private final Map<Class<? extends Event>, EventListener> listeners;

    // Concrete Listener for Login - could be anonymous
    private class LoginListener implements EventListener<LoginEvent> {
        public void onEvent(LoginEvent event) {
            System.out.println("Login");
        }
    }

    // Concrete Listener for Logout - could be anonymous        
    private class LogoutListener implements EventListener<LogoutEvent> {
        public void onEvent(LogoutEvent event) {
            System.out.println("Logout");
        }
    }

    public Foo() {
        @SuppressWarnings("rawtypes")
        Map<Class<? extends Event>, EventListener> temp  = new HashMap<>();
        // LoginEvents will be routed to LoginListener
        temp.put(LoginEvent.class, new LoginListener());
        // LogoutEvents will be routed to LoginListener
        temp.put(LogoutEvent.class, new LogoutListener());
        listeners = Collections.unmodifiableMap(temp);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onEvent(Event event) {
        // Maps make it easy to delegate, but again, this could be anything
        if (listeners.containsKey(event.getClass())) {
            listeners.get(event.getClass()).onEvent(event);
        } else {
            /* Screams if a unsupported event gets passed
             * Comment this line if you want to ignore
             * unsupported events
             */
            throw new IllegalArgumentException("Event not supported");
        }
    }

    public static void main(String[] args) {
        Foo foo = new Foo();
        System.out.println(foo instanceof EventListener); // true
        foo.onEvent(new LoginEvent()); // Login
        foo.onEvent(new LogoutEvent()); // Logout
    }
}

The suppress warnings are there because we are "abusing" type erasure and delegating to two different event listeners based on the event concrete type. I have chosen to do it using a HashMap and the run-time Event class, but there are a lot of other possible implementations. You could use anonymous inner classes like @user949300 suggested, you could include a getEventType discriminator on the Event class to know what do to with each event and so on.

By using this code for all effects you are creating a single EventListener able to handle two kinds of events. The workaround is 100% self-contained (no need to expose the internal EventListeners).

Finally, there is one last issue that may bother you. At compile time Foo type is actually EventListener. Now, API methods out of your control may be expecting parametrized EventListeners:

public void addLoginListener(EventListener<LoginEvent> event) { // ...
// OR
public void addLogoutListener(EventListener<LogoutEvent> event) { // ...

Again, at run-time both of those methods deal with raw EventListeners. So by having Foo implement a raw interface the compiler will be happy to let you get away with just a type safety warning (which you can disregard with @SuppressWarnings("unchecked")):

eventSource.addLoginListener(foo); // works

While all of this may seem daunting, just repeat to yourself "The compiler is trying to trick me (or save me); there is no spoon <T>. Once you scratch your head for a couple of months trying to make legacy code written before Java 1.5 work with modern code full of type parameters, type erasure becomes second nature to you.

Anthony Accioly
  • 21,918
  • 9
  • 70
  • 118
16

You need to use inner or anonymous classes. For instance:

class Foo {
   public EventListener<X> asXListener() {
      return new EventListener<X>() {
          // code here can refer to Foo
      };
   }


  public EventListener<Y> asYListener() {
      return new EventListener<Y>() {
          // code here can refer to Foo
      };
   }
}
user5193682
  • 260
  • 1
  • 11
user949300
  • 15,364
  • 7
  • 35
  • 66
  • 2
    Is this a LIMITATION of java? Or is it done so intentionally? –  Jun 16 '15 at 08:49
  • 2
    @Suresh Kumar It is a limitation of Java due to type erasure of generics. Note that in Java 8 the syntax for the anonymous classes will be shorter and arguably simpler. – user949300 Jun 16 '15 at 19:58
2

This is not possible. But for that you could create two different classes that implement EventListener interface with two different arguments.

public class Login implements EventListener<LoginEvent> {

    public void onEvent(LoginEvent event) {
        // TODO Auto-generated method stub
    }
}

public class Logout implements EventListener<LogoutEvent> {

    public void onEvent(LogoutEvent event) {
        // TODO Auto-generated method stub      
    }   
}
ankitkanojia
  • 3,072
  • 4
  • 22
  • 35
ravi.patel
  • 119
  • 4