如何多次实现相同的界面,但具有不同的泛型?

Cli*_*ote 27 java generics interface

我有以下界面,我想在我的类中多次实现:

public interface EventListener<T extends Event>
{
    public void onEvent(T event);
}
Run Code Online (Sandbox Code Playgroud)

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

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

    @Override
    public void onEvent(LoginEvent event)
    {

    }

    @Override
    public void onEvent(LogoutEvent event)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

但是,这给了我错误:Duplicate class com.foo.EventListener在线:

class Foo implements EventListener<LoginEvent>, EventListener<LogoutEvent>
Run Code Online (Sandbox Code Playgroud)

是否可以使用不同的泛型实现两次接口?如果没有,那么我能做到的最接近的事情是如何实现我在这里尝试做的事情?

Ant*_*oly 20

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

很不幸的是,不行.您无法两次实现相同接口的原因是因为类型擦除.编译器将处理类型参数,运行时EventListener<X>只是一个EventListener


如果没有,那么我能做到的最接近的事情是如何实现我在这里尝试做的事情?

类型擦除可以对我们有利.一旦你知道EventListener<X>并且在运行时EventListener<Y>只是原始的EventListener,那么编写一个EventListener可以处理不同类型的东西比你想象的容易Events.波纹管是通过了一个解决方案IS-A测试EventListener和正确地同时处理LoginLogout通过简单的授权的手段事件:

@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
    }
}
Run Code Online (Sandbox Code Playgroud)

抑制警告是存在的,因为我们"滥用"类型擦除并根据事件具体类型委托给两个不同的事件监听器.我选择使用a HashMap和运行时事件来做class,但是还有很多其他可能的实现.您可以使用@ user949300建议的匿名内部类,您可以getEventType在Event类中包含一个鉴别器,以了解每个事件的作用等等.

通过将此代码用于所有效果,您可以创建一个EventListener能够处理两种事件的单一事件.解决方法是100%自包含(不需要暴露内部EventListeners).

最后,最后一个问题可能会让您烦恼.在编译时,Foo类型实际上是EventListener.现在,您无法控制的API方法可能需要参数化EventListener:

public void addLoginListener(EventListener<LoginEvent> event) { // ...
// OR
public void addLogoutListener(EventListener<LogoutEvent> event) { // ...
Run Code Online (Sandbox Code Playgroud)

同样,在运行时,这两种方法都处理原始EventListeners.因此,通过Foo实现原始接口,编译器将很乐意让您远离类型安全警告(您可以忽略它@SuppressWarnings("unchecked")):

eventSource.addLoginListener(foo); // works
Run Code Online (Sandbox Code Playgroud)

虽然所有这些看起来令人生畏,但只要重复一遍"编译器试图欺骗我(或拯救我);没有 <T>.一旦你试图在Java 1.5使用现代代码充满类型参数的情况下编写遗留代码几个月之后,类型擦除就成了你的第二天性.


use*_*300 11

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

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
      };
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是java的限制吗?还是故意这么做? (2认同)
  • @Suresh Kumar由于泛型的类型擦除,它是Java的限制.请注意,在Java 8中,匿名类的语法将更短,并且可以说更简单. (2认同)