J'ai une question sur les génériques Java à laquelle j'espérais que quelqu'un pourrait répondre. Considérons le code suivant:
public interface Event{}
public class AddressChanged implements Event{}
public class AddressDiscarded implements Event{}
public interface Handles<T extends Event>{
public void handle(T event);
}
Je veux implémenter cette interface Handles comme ceci:
public class AddressHandler implements Handles<AddressChanged>, Handles<AddressDiscarded>{
public void handle(AddressChanged e){}
public void handle(AddressDiscarded e){}
}
Mais Java ne permet pas d'implémenter deux fois Handles en utilisant le générique. J'ai pu accomplir cela avec C #, mais je ne peux pas trouver de solution de contournement en Java sans utiliser Reflection ou instanceof et le casting.
Existe-t-il un moyen en Java d'implémenter l'interface Handles en utilisant les deux interfaces génériques? Ou peut-être un autre moyen d'écrire l'interface Handles afin que le résultat final puisse être obtenu?
Vous ne pouvez pas faire cela en Java. Vous ne pouvez implémenter qu'une réalisation concrète de la même interface générique. Je ferais ceci à la place:
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){}
}
Après @Amir Raminfar, vous pouvez utiliser visiteur pattern
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){}
}
Non, car différents types génériques "concrets" en Java sont compilés en le même type. L'interface réelle que votre objet implémentera est:
public interface Handles {
public void handle(Event event);
}
Et, évidemment, vous ne pouvez pas avoir deux méthodes différentes avec une signature identique ...
Autant que je sache, vous ne pouvez pas le faire, car lors de la compilation du code source en Java, ils se résument à handle(Event)
, ce qui rend la méthode ambiguë.
Les informations génériques ne sont pas disponibles lors de l'exécution en Java, contrairement à C #. Voilà pourquoi cela fonctionne comme vous le décrivez.
Vous devrez changer les noms de méthodes pour les rendre uniques, comme handleAddressChanged
et handleAddressDiscarded
.
C’est en effet l’un des points faibles des génériques Java.
Une implémentation comme celle-ci ne fonctionnera pas à cause des contraintes de la spécification Java . Mais si vous n'avez pas peur d'utiliser AOP ou une sorte de conteneur IOC, vous pouvez utiliser des annotations pour cela. Que vos Aspects ou le conteneur pourraient gérer l’infrastructure de messagerie et appeler les méthodes que vous annotez.
Vous devez d'abord créer les annotations.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventConsumer {}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Handles{}
Vous pouvez annoter votre classe comme ça:
@EventConsumer
public class AddressHandler{
@Handles
public void handle(AddressChanged e){}
@Handles
public void handle(AddressDiscarded e){}
}
Malheureusement non. La solution habituelle (grosse, laide, rapide) est de créer une interface Handles
(c'est-à-dire HandlesAddressChange
, HandlesAddressDiscarded
) et de donner à chacun une méthode différente (handleAddressChange(...)
, handleAddressDiscarded()
).
De cette façon, le runtime Java peut les différencier.
Ou vous pouvez utiliser des classes anonymes.
Cela n'est pas autorisé car Java efface les signatures génériques lors de la compilation. La méthode d'interface aura en fait la signature
public void handle(Object event);
Donc, vous avez deux choix. Soit implémenter des gestionnaires distincts pour différents événements:
public class AddressChangedHandler implements Handles<AddressChanged>{ /* ... */ }
public class AddressDiscardedHandler implements Handles<AddressDiscarded>{ /* ... */ }
ou implémentez un seul gestionnaire pour tous mais vérifiez le type de l'événement entrant:
public void handle(Event e){
if (e instanceof AddressChanged) {
handleAdressChanged(e);
}
else if (e instanceof AddressDiscareded) {
handleAdressDiscarded(e);
}
}