如何多次实现相同的接口,但使用不同的泛型?

2022-09-01 02:09:45

我有以下接口,我想在我的类中多次实现它:

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

现在,我希望能够通过以下方式实现此接口:

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

    @Override
    public void onEvent(LoginEvent event)
    {

    }

    @Override
    public void onEvent(LogoutEvent event)
    {

    }
}

但是,这给了我一个错误:在行上:Duplicate class com.foo.EventListener

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

是否可以使用不同的泛型实现接口两次?如果没有,我能做的下一个最接近的事情是什么,以实现我在这里想要做的事情?


答案 1

是否可以使用不同的泛型实现接口两次

很遗憾,没有。您无法两次实现同一接口的原因是类型擦除。编译器将处理类型参数,而运行时只是一个EventListener<X>EventListener


如果没有,我能做的下一个最接近的事情是什么,以实现我在这里想要做的事情?

类型擦除可以对我们有利。一旦你知道这一点,并且在运行时只是原始的,那么编写一个可以处理不同类型的.Bellow 是一种解决方案,它通过简单的委派来通过测试并正确处理这两个事件:EventListener<X>EventListener<Y>EventListenerEventListenerEventsIS-AEventListenerLoginLogout

@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
    }
}

禁止显示警告之所以存在,是因为我们正在“滥用”类型擦除,并根据事件具体类型委托给两个不同的事件侦听器。我选择使用 a 和 运行时 Event ,但是还有很多其他可能的实现。您可以使用匿名内部类(如@user949300建议的那样,可以在 Event 类上包含一个鉴别器,以了解如何处理每个事件,依此类推。HashMapclassgetEventType

通过对所有效果使用此代码,您可以创建一个能够处理两种事件的单个效果。解决方法是 100% 独立(无需公开内部 )。EventListenerEventListeners

最后,还有最后一个问题可能会困扰您。在编译时,类型实际上是 。现在,不受您控制的 API 方法可能期望参数化:FooEventListenerEventListener

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

同样,在运行时,这两种方法都处理原始 s。因此,通过实现原始接口,编译器将很乐意让您只得到一个类型安全警告(您可以使用以下命令忽略):EventListenerFoo@SuppressWarnings("unchecked")

eventSource.addLoginListener(foo); // works

虽然所有这些可能看起来令人生畏,但只要对自己重复一遍“编译器试图欺骗我(或拯救我);没有勺子。一旦你挠了几个月的头,试图让Java 1.5之前编写的遗留代码与充满类型参数的现代代码一起使用,类型擦除就成了你的第二天性。<T>


答案 2

您需要使用内部类或匿名类。例如:

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
      };
   }
}