Как реализовать один и тот же интерфейс несколько раз, но с разными дженериками?
У меня есть следующий интерфейс, который я хочу реализовать несколько раз в своих классах:
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
}
}