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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22138475/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 13:50:44  来源:igfitidea点击:

How to implement the same interface multiple times, but with different generics?

javagenericsinterface

提问by Click Upvote

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.EventListeneron the line:

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

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?

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

采纳答案by user949300

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

回答by Anthony Accioly

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

抱歉不行。不能两次实现同一个接口的原因是类型擦除。编译器会处理类型参数,而运行时EventListener<X>只是一个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 EventListenerat run-time, it is easier than you think to write an EventListenerthat can deal with different kinds of Events. Bellow is a solution that passes the IS-Atest for EventListenerand correctly handles both Loginand Logoutevents by means of simple delegation:

类型擦除对我们有利。一旦你知道这一点EventListener<X>并且在运行时EventListener<Y>只是原始的EventListener,编写一个EventListener可以处理不同类型的Events. Bellow 是一种通过简单委托通过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
    }
}

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 HashMapand 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 getEventTypediscriminator on the Event class to know what do to with each event and so on.

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

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

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

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

最后,还有最后一个问题可能会困扰您。在编译时Foo类型实际上是EventListener. 现在,您无法控制的 API 方法可能需要参数化的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 Fooimplement 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")):

同样,在运行时,这两种方法都处理原始EventListeners。因此,通过Foo实现一个原始接口,编译器会很乐意让您摆脱类型安全警告(您可以忽略@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.

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

回答by ravi.patel

This is not possible. But for that you could create two different classesthat implement EventListenerinterface with two different arguments.

这不可能。但是为此,您可以创建两个不同的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      
    }   
}