在Java中实现通用接口

30

我希望你能帮忙翻译一个Java泛型问题。请考虑以下代码:

public interface Event{}
public class AddressChanged implements Event{}
public class AddressDiscarded implements Event{}

public interface Handles<T extends Event>{
    public void handle(T event);
}

我希望能够像这样实现Handles接口:

public class AddressHandler implements Handles<AddressChanged>, Handles<AddressDiscarded>{
    public void handle(AddressChanged e){}
    public void handle(AddressDiscarded e){}
}

但是Java不允许使用泛型实现两次Handles接口。我可以在C#中完成这个任务,但是在Java中没有找到不使用反射或instanceof和强制类型转换的解决方法。

是否有一种方式可以在Java中使用两个泛型接口来实现Handles接口?或者也许有另一种方式来编写Handles接口以达到最终目标?

8个回答

20

跟随 @Amir Raminfar 的方法,你可以使用 访问者模式

interface Event{
 void accept(Visitor v);
}
interface Visitor {
 void visitAddressChanged(AddressChanged a);
 void visitAddressDiscarded(AddressDiscarded a);
}

class AddressChanged implements Event{
 @Override
 public void accept(Visitor v) {
  v.visitAddressChanged(this);
 } 
}

class AddressDiscarded implements Event{
 @Override
 public void accept(Visitor v) {
  v.visitAddressDiscarded(this);
 } 
}

class AddressHandler implements Visitor {
    void handle(Event e){
       e.accept(this);
     }
    public void visitAddressChanged(AddressChanged e){}
    public void visitAddressDiscarded(AddressDiscarded e){}
}

+1,但是在这个模式中,事件“接受”访问者,而访问者“访问”事件 :) - sly7_7
@robev,我忘记在那一行改名字了。谢谢。已修复。 - Stan Kurilin

10

在Java中你不能这样做。你只能实现同一个泛型接口的一个具体实现。我建议你这样做:

public class AddressHandler implements Handles<Event>{
    public void handle(Event e){
      if(e instanceof AddressDiscarded){
         handleDiscarded(e);
      } else if(e instanceof AddressChanged){
         handleChanged(e);
      }
    }
    public void handleDiscarded(AddressDiscarded e){}
    public void handleChanged(AddressChanged e){}
}

4
这是有争议的...... 这个"instanceof cascade"是一种非常程序化的范例。当然它能用,但在我看来不是很好的风格。虽然使用不同的方法名称也不完美,但这可能只是个人口味问题。 - Daniel Schneller
2
这是我会做的事情,你不必做 :) - Amir Raminfar
5
@Don: 但不是同一个通用接口的多个实例。这就是我们所讨论的。 :P - cdhowie

2
不行,因为在Java中,不同的“具体”泛型类型编译成了相同的类型。您对象实际实现的接口是:
public interface Handles {
    public void handle(Event event);
}

当然,你不能拥有两个具有相同签名的不同方法...

实际上,擦除将是 public void handle(Event event); - Sean Patrick Floyd

1

据我所知,你不能这样做,因为在Java中编译源代码时,两者都会归结为handle(Event),使该方法模糊不清。

与C#相比,Java在运行时不提供通用信息。这就是为什么它按照您描述的方式工作的原因。

您必须更改方法名称以使其唯一,例如handleAddressChangedhandleAddressDiscarded

这确实是Java泛型的薄弱点之一。


这实际上是一个编译时问题。您不需要了解对象的运行时属性(参数甚至可以是“null”!)来区分方法。 - Tom Hawtin - tackline

0

很遗憾,通常的解决方案(臃肿、丑陋、快速)是创建一个Handles接口(即HandlesAddressChangeHandlesAddressDiscarded),并为每个接口提供不同的方法(handleAddressChange(...)handleAddressDiscarded())。

这样,Java运行时就可以区分它们。

或者您可以使用匿名类。


0

这是不允许的,因为Java在编译期间会擦除泛型签名。接口方法实际上将具有该签名

public void handle(Object event);

所以你有两个选择。要么为不同的事件实现单独的处理程序:

public class AddressChangedHandler implements Handles<AddressChanged>{ /* ... */ }
public class AddressDiscardedHandler implements Handles<AddressDiscarded>{ /* ... */ }

或者实现一个处理程序来处理所有的事件,但检查传入事件的类型:

public void handle(Event e){
  if (e instanceof AddressChanged) {
     handleAdressChanged(e);
  }
  else if (e instanceof AddressDiscareded) {
     handleAdressDiscarded(e);
  }
}

0
一个这样的实现不会起作用,因为它受到Java规范的限制。 但是,如果你不怕使用AOP或某种IOC容器,你可以使用注解来完成。然后你的Aspect或容器可以管理消息基础设施并调用你注释的方法。
首先,你需要创建注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventConsumer {}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Handles{}

你可以这样注释你的类:
@EventConsumer
public class AddressHandler{
    @Handles
    public void handle(AddressChanged e){}
    @Handles
    public void handle(AddressDiscarded e){}
}

0

如果您不介意使用(小型)库,这里有一个我编写的可以解决您问题的库:

https://github.com/bertilmuth/requirementsascode

你可以这样构建一个模型。
Model.builder()
  .on(AddressChanged.class).system(this::handleAddressChanged)
  .on(AddressDiscarded.class).system(this::handleAddressDiscarded)
  .build()

然后运行它。

如何确切地做到这一点在网站上有描述。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接