Как реализовать один и тот же интерфейс несколько раз, но с разными дженериками?

У меня есть следующий интерфейс, который я хочу реализовать несколько раз в своих классах:

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

Вам нужно использовать внутренние или анонимные классы. например.

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

Ответ 2

Возможно ли реализовать интерфейс дважды с помощью разных генериков

К сожалению нет. Причина, по которой вы не можете реализовать один и тот же интерфейс дважды, связана с стиранием типа. Компилятор будет обрабатывать параметры типа, а время выполнения EventListener<X> - это просто EventListener


Если нет, то, что следующая ближайшая вещь, которую я могу сделать, чтобы достичь того, что я пытаюсь сделать здесь?

Тип стирания может работать в нашу пользу. Как только вы знаете, что EventListener<X> и EventListener<Y> являются только raw EventListener во время выполнения, это проще, чем вы думаете написать EventListener, который может иметь дело с различными типами Events. Bellow - это решение, которое проходит тест IS-A для EventListener и корректно обрабатывает события Login и Logout посредством простого делегирования:

@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, вы могли бы включить дискриминатор getEventType в класс Event, чтобы знать, что делать с каждым событием, и так далее.

Используя этот код для всех эффектов, вы создаете один EventListener, способный обрабатывать два типа событий. Обходной путь является 100% автономным (нет необходимости выставлять внутренний EventListeners).

Наконец, есть одна последняя проблема, которая может вас беспокоить. Во время компиляции Foo тип фактически EventListener. Теперь API-методы из вашего контроля могут ожидать параметризованные EventListener s:

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

Опять же, во время выполнения оба этих метода относятся к raw EventListener s. Таким образом, имея Foo реализовать необработанный интерфейс, компилятор будет рад позволить вам избежать просто предупреждения о безопасности типа (которое вы можете игнорировать с помощью @SuppressWarnings("unchecked")):

eventSource.addLoginListener(foo); // works

Хотя все это может показаться сложным, просто повторите сам себе. "Компилятор пытается обмануть меня (или спасти меня), нет spoon <T>. Как только вы поцарапаете себе голову пару месяцев, пытаясь сделать устаревший код написанным до того, как Java 1.5 работает с современным кодом, полным параметров типа, тип стирания становится для вас второй натурой.

Ответ 3

Это невозможно. Но для этого вам нужно создать два разных класса, которые реализуют интерфейс 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      
    }   
}