web-dev-qa-db-fra.com

Implémentation d'une interface générique en Java

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?

26
whistlenuts

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){}
}
10
Amir Raminfar

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){}
}
18
Stan Kurilin

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

2
cdhowie

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.

1
Daniel Schneller

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){}
}
0
Olivier Heidemann

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.

0
Aaron Digulla

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);
  }
}
0
Andreas_D