web-dev-qa-db-fra.com

Comment créer une classe Java qui implémente une interface avec deux types génériques?

J'ai une interface générique

public interface Consumer<E> {
    public void consume(E e);
}

J'ai une classe qui consomme deux types d'objets, alors j'aimerais faire quelque chose comme:

public class TwoTypesConsumer implements Consumer<Tomato>, Consumer<Apple>
{
   public void consume(Tomato t) {  .....  }
   public void consume(Apple a) { ...... }
}

Apparemment je ne peux pas faire ça.

Je peux bien sûr mettre en œuvre l’envoi moi-même, par exemple.

public class TwoTypesConsumer implements Consumer<Object> {
   public void consume(Object o) {
      if (o instanceof Tomato) { ..... }
      else if (o instanceof Apple) { ..... }
      else { throw new IllegalArgumentException(...) }
   }
}

Mais je suis à la recherche de la solution de vérification et de répartition des types fournie par les génériques au moment de la compilation.

La meilleure solution à laquelle je puisse penser est de définir des interfaces séparées, par exemple.

public interface AppleConsumer {
   public void consume(Apple a);
}

Sur le plan fonctionnel, cette solution est satisfaisante, je pense. C'est juste verbeux et moche.

Des idées? 

146
daphshez

Considérer l'encapsulation:

public class TwoTypesConsumer {
    private TomatoConsumer tomatoConsumer = new TomatoConsumer();
    private AppleConsumer appleConsumer = new AppleConsumer();

    public void consume(Tomato t) { 
        tomatoConsumer.consume(t);
    }

    public void consume(Apple a) { 
        appleConsumer.consume(a);
    }

    public static class TomatoConsumer implements Consumer<Tomato> {
        public void consume(Tomato t) {  .....  }
    }

    public static class AppleConsumer implements Consumer<Apple> {
        public void consume(Apple a) {  .....  }
    }
}

Si la création de ces classes internes statiques vous dérange, vous pouvez utiliser des classes anonymes:

public class TwoTypesConsumer {
    private Consumer<Tomato> tomatoConsumer = new Consumer<Tomato>() {
        public void consume(Tomato t) {
        }
    };

    private Consumer<Apple> appleConsumer = new Consumer<Apple>() {
        public void consume(Apple a) {
        }
    };

    public void consume(Tomato t) {
        tomatoConsumer.consume(t);
    }

    public void consume(Apple a) {
        appleConsumer.consume(a);
    }
}
70
Steve McLeod

En raison de l'effacement de type, vous ne pouvez pas implémenter deux fois la même interface (avec des paramètres de type différents).

34
Shimi Bandiel

Voici une solution possible basée sur celle de Steve McLeod :

public class TwoTypesConsumer {
    public void consumeTomato(Tomato t) {...}
    public void consumeApple(Apple a) {...}

    public Consumer<Tomato> getTomatoConsumer() {
        return new Consumer<Tomato>() {
            public void consume(Tomato t) {
                consumeTomato(t);
            }
        }
    }

    public Consumer<Apple> getAppleConsumer() {
        return new Consumer<Apple>() {
            public void consume(Apple a) {
                consumeApple(t);
            }
        }
    }
}

La condition implicite de la question était les objets Consumer<Tomato> et Consumer<Apple> qui partagent l'état. La nécessité des objets Consumer<Tomato>, Consumer<Apple> provient d'autres méthodes qui les attendent en tant que paramètres. J'ai besoin d'une classe pour les implémenter afin de partager l'état. 

L'idée de Steve était d'utiliser deux classes internes, chacune implémentant un type générique différent. 

Cette version ajoute des accesseurs pour les objets qui implémentent l'interface Consumer, qui peuvent ensuite être transmis aux autres méthodes qui les attendent.

9
daphshez

Au moins, vous pouvez apporter une petite amélioration à votre mise en œuvre de dispatch en procédant comme suit:

public class TwoTypesConsumer implements Consumer<Fruit> {

Fruit étant un ancêtre de la tomate et de la pomme.

7
Buhb

juste tombé sur cela. Il m'est arrivé de rencontrer le même problème, mais je l'ai résolu différemment: Je viens de créer une nouvelle interface comme celle-ci.

public interface TwoTypesConsumer<A,B> extends Consumer<A>{
    public void consume(B b);
}

malheureusement, ceci est considéré comme Consumer<A> et NON comme Consumer<B> par rapport à toute la logique. Vous devez donc créer un petit adaptateur pour le second consommateur comme celui-ci dans votre classe.

public class ConsumeHandler implements TwoTypeConsumer<A,B>{

    private final Consumer<B> consumerAdapter = new Consumer<B>(){
        public void consume(B b){
            ConsumeHandler.this.consume(B b);
        }
    };

    public void consume(A a){ //...
    }
    public void conusme(B b){ //...
    }
}

si un Consumer<A> est nécessaire, vous pouvez simplement passer this et si Consumer<B> est nécessaire simplement passer consumerAdapter

3
Rafael T

Vous ne pouvez pas le faire directement dans une classe car la définition de classe ci-dessous ne peut pas être compilée en raison de l'effacement de types génériques et de la déclaration d'interface en double. 

class TwoTypesConsumer implements Consumer<Apple>, Consumer<Tomato> { 
 // cannot compile
 ...
}

Toute autre solution permettant de regrouper les mêmes opérations de consommation dans une classe nécessite de définir votre classe comme suit:

class TwoTypesConsumer { ... }

ce qui est inutile car vous devez répéter/dupliquer la définition des deux opérations et elles ne seront pas référencées à partir de l'interface. IMHO faire cela est une mauvaise et petite duplication de code que j'essaie d'éviter.

Cela peut également indiquer qu'il existe trop de responsabilités dans une classe pour consommer deux objets différents (s'ils ne sont pas couplés). 

Toutefois, ce que je peux faire et ce que vous pouvez faire est d’ajouter un objet usine explicite pour créer des consommateurs connectés de la manière suivante:

interface ConsumerFactory {
     Consumer<Apple> createAppleConsumer();
     Consumer<Tomato> createTomatoConsumer();
}

Si en réalité ces types sont vraiment couplés (liés) alors je recommanderais de créer une implémentation de la manière suivante:

class TwoTypesConsumerFactory {

    // shared objects goes here

    private class TomatoConsumer implements Consumer<Tomato> {
        public void consume(Tomato tomato) {
            // you can access shared objects here
        }
    }

    private class AppleConsumer implements Consumer<Apple> {
        public void consume(Apple apple) {
            // you can access shared objects here
        }
    }


    // It is really important to return generic Consumer<Apple> here
    // instead of AppleConsumer. The classes should be rather private.
    public Consumer<Apple> createAppleConsumer() {
        return new AppleConsumer();
    }

    // ...and the same here
    public Consumer<Tomato> createTomatoConsumer() {
        return new TomatoConsumer();
    }
}

L'avantage est que la classe de fabrique connaît les deux implémentations, qu'il existe un état partagé (si nécessaire) et que vous pouvez renvoyer davantage de consommateurs couplés si nécessaire. Il n'y a pas de déclaration de méthode consume répétée qui ne soit pas dérivée de l'interface.

Veuillez noter que chaque consommateur peut être une classe indépendante (toujours privée) s’ils ne sont pas complètement liés.

L'inconvénient de cette solution est une complexité de classe plus élevée (même s'il peut s'agir d'un fichier Java unique) et pour accéder à la méthode consume, vous avez besoin d'un appel supplémentaire, au lieu de:

twoTypesConsumer.consume(Apple)
twoTypesConsumer.consume(tomato)

tu as:

twoTypesConsumerFactory.createAppleConsumer().consume(Apple);
twoTypesConsumerFactory.createTomatoConsumer().consume(tomato);

Pour résumer, vous pouvez définir 2 consommateurs génériques dans une classe de niveau supérieur à l'aide de 2 classes internes, mais en cas d'appel, vous devez d'abord obtenir une référence au consommateur implementation approprié, car il ne peut s'agir d'un seul objet consommateur. .

1
kitarek

Désolé de répondre à de vieilles questions, mais je l’aime vraiment Essayez cette option:

public class MegaConsumer implements Consumer<Object> {

  Map<Class, Consumer> consumersMap = new HashMap<>();
  Consumer<Object> baseConsumer = getConsumerFor(Object.class);

  public static void main(String[] args) {
    MegaConsumer megaConsumer = new MegaConsumer();

    //You can load your customed consumers
    megaConsumer.loadConsumerInMapFor(Tomato.class);
    megaConsumer.consumersMap.put(Apple.class, new Consumer<Apple>() {
        @Override
        public void consume(Apple e) {
            System.out.println("I eat an " + e.getClass().getSimpleName());
        }
    });

    //You can consume whatever
    megaConsumer.consume(new Tomato());
    megaConsumer.consume(new Apple());
    megaConsumer.consume("Other class");
  }

  @Override
  public void consume(Object e) {
    Consumer consumer = consumersMap.get(e.getClass());
    if(consumer == null) // No custom consumer found
      consumer = baseConsumer;// Consuming with the default Consumer<Object>
    consumer.consume(e);
  }

  private static <T> Consumer<T> getConsumerFor(Class<T> someClass){
    return t -> System.out.println(t.getClass().getSimpleName() + " consumed!");
  }

  private <T> Consumer<T> loadConsumerInMapFor(Class<T> someClass){
    return consumersMap.put(someClass, getConsumerFor(someClass));
  }
}

Je pense que c'est ce que vous recherchez.

Vous obtenez cette sortie:

Tomate consommée!

Je mange une pomme

Chaîne consommée!

0
Awes0meM4n

Une autre alternative pour éviter l'utilisation de plus de classes. (exemple utilisant Java8 +)

// Mappable.Java
public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

// TwoMappables.Java
public interface TwoMappables {
    default Mappable<A> mapableA() {
         return new MappableA();
    }

    default Mappable<B> mapableB() {
         return new MappableB();
    }

    class MappableA implements Mappable<A> {}
    class MappableB implements Mappable<B> {}
}

// Something.Java
public class Something implements TwoMappables {
    // ... business logic ...
    mapableA().mapTo(A);
    mapableB().mapTo(B);
}
0
winter