【问题标题】:How to implement the same interface multiple times, but with different generics? [duplicate]如何多次实现相同的接口,但使用不同的泛型? [复制]
【发布时间】:2020-07-25 06:48:12
【问题描述】:

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

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>

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

【问题讨论】:

  • 派生EventListener&lt;OneOfThoseEventTypes&gt;的子接口,并让这个类实现它? (我不知道它是否会起作用,我只是把它作为一种可能的解决方法扔掉。)
  • @keshlam 在这种情况下,我仍然无法使用两种单独的方法来处理登录和注销事件。
  • JFYI 您可以在 C# 中通过显式接口实现来实现这一点。
  • 使用编译时类型检查示例检查此功能样式 - stackoverflow.com/a/60466413/4121845

标签: java generics interface


【解决方案1】:

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

很遗憾,没有。您不能两次实现相同接口的原因是类型擦除。编译器将处理类型参数,运行时EventListener&lt;X&gt; 只是一个EventListener


如果不是,我能做的下一个最接近的事情是什么?

类型擦除对我们有利。一旦您知道EventListener&lt;X&gt;EventListener&lt;Y&gt; 在运行时只是原始EventListener,编写一个可以处理不同类型EventsEventListener 比您想象的要容易。 Bellow 是一个通过 EventListenerIS-A 测试并通过简单委托正确处理 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
    }
}

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

通过对所有效果使用此代码,您将创建一个能够处理两种事件的EventListener。解决方法是 100% 自包含(无需公开内部 EventListeners)。

最后,还有一个问题可能会困扰您。在编译时Foo 类型实际上是EventListener。现在,您无法控制的 API 方法可能需要参数化 EventListeners:

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

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

eventSource.addLoginListener(foo); // works

虽然所有这些看起来令人生畏,但只要对自己重复一遍“编译器试图欺骗我(或拯救我);没有 spoon &lt;T&gt;。一旦你为几个月试图让 Java 1.5 之前编写的遗留代码与充满类型参数的现代代码一起工作,类型擦除成为你的第二天性。

【讨论】:

  • +1 用于类型擦除
  • 这在 java 8 中是否可行? :(
  • 同一个界面可以跨两次吗? :-)。晦涩的赫拉克略名言。
  • @user949300,编译器很聪明,说The interface xxx cannot be implemented more than once with different arguments会编译失败。但是请看一下我的代码,看看如何使它适用于原始类型。
  • @Click Upvote:不,类型擦除仍然是 Java 8 上泛型的机制。实现你想要的唯一方法是重写整个 Java 泛型系统并破坏与旧版本的兼容性Java(就像微软在 .NET 2.0 上引入泛型时所做的那样)。我怀疑 Java 会采取这种行动。
【解决方案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
      };
   }
}

【讨论】:

  • 这是java的限制吗?还是有意为之?
  • @Suresh Kumar 由于泛型类型擦除,这是 Java 的一个限制。请注意,在 Java 8 中,匿名类的语法会更短,并且可以说更简单。
【解决方案3】:

这是不可能的。 但为此,您可以创建两个不同的 classes 来实现具有两个不同参数的 EventListener 接口。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-27
    • 1970-01-01
    相关资源
    最近更新 更多