Hoe een Java-klasse te maken die één interface met twee generieke typen implementeert?

Ik heb een generieke interface

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

Ik heb een klas die twee soorten objecten verbruikt, dus ik zou graag iets willen doen als:

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

Blijkbaar kan ik dat niet doen.

Ik kan natuurlijk de verzending zelf implementeren, b.v.

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

Maar ik ben op zoek naar de compilertijd-typecontrole- en verzendoplossing die generiek bieden.

De beste oplossing die ik kan bedenken is om afzonderlijke interfaces te definiëren, b.g.

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

Functioneel, deze oplossing is OK, denk ik. Het is gewoon uitgebreid en lelijk.

alle ideeën?


Antwoord 1, Autoriteit 100%

Overweeg de inkapseling:

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) {  .....  }
    }
}

Als het maken van deze statische innerlijke klassen u stoort, kunt u anonieme klassen gebruiken:

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

Antwoord 2, Autoriteit 54%

Vanwege het wissen van het type kunt u niet twee keer dezelfde interface implementeren (met verschillende typeparameters).


Antwoord 3, Autoriteit 15%

Hier is een mogelijke oplossing op basis van Steve McLeod’s One :

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

De impliciete vereiste van de vraag was Consumer<Tomato>en Consumer<Apple>objecten die toestand zijn. De behoefte aan Consumer<Tomato>, Consumer<Apple>objecten komen van andere methoden die deze verwachten als parameters. Ik heb één klasse nodig die ze beiden hebben om de staat te delen.

Steve’s idee was om twee innerlijke klassen te gebruiken, die elk een ander generiek type implementeren.

Deze versie voegt getters toe voor de objecten die de consumenteninterface implementeren, die vervolgens kunnen worden doorgegeven aan andere methoden die ze verwachten.


Antwoord 4, autoriteit 9%

U kunt in ieder geval een kleine verbetering aanbrengen in uw implementatie van verzending door iets als het volgende te doen:

public class TwoTypesConsumer implements Consumer<Fruit> {

Fruit is een voorouder van tomaat en appel.


Antwoord 5, autoriteit 4%

Ik kwam dit zojuist tegen. Het gebeurde gewoon dat ik hetzelfde probleem had, maar ik heb het op een andere manier opgelost:
Ik heb zojuist een nieuwe interface zoals deze gemaakt

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

helaas wordt dit beschouwd als Consumer<A>en NIET als Consumer<B>tegen alle logica in. Dus je moet een kleine adapter maken voor de tweede consument zoals deze in je klas

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){ //...
    }
}

als een Consumer<A>nodig is, kunt u gewoon thisdoorgeven, en als Consumer<B>nodig is, geeft u gewoon door consumerAdapter


Antwoord 6, autoriteit 4%

In Functionele stijl is het vrij eenvoudig om dit te doen zonder de interface te implementeren en het controleert ook het type compileertijd.

Onze functionele interface om entiteit te consumeren

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

onze manager om de entiteit op de juiste manier te verwerken en te consumeren

public class Manager {
    public <E> void process(Consumer<E> consumer, E entity) {
        consumer.consume(entity);
    }
    public void consume(Tomato t) {
        // Consume Tomato
    }
    public void consume(Apple a) {
        // Consume Apple
    }
    public void test() {
        process(this::consume, new Tomato());
        process(this::consume, new Apple());
    }
}

Antwoord 7

U kunt dit niet rechtstreeks in één klasse doen, aangezien de onderstaande klassedefinitie niet kan worden gecompileerd vanwege het wissen van generieke typen en dubbele interfacedeclaratie.

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

Elke andere oplossing om dezelfde verbruiksbewerkingen in één klasse in te pakken, vereist om uw klasse te definiëren als:

class TwoTypesConsumer { ... }

wat zinloos is omdat je de definitie van beide bewerkingen moet herhalen/dupliceren en er niet naar wordt verwezen vanuit de interface. IMHO dit te doen is een slechte kleine en codeduplicatie die ik probeer te vermijden.

Dit kan ook een indicatie zijn dat er te veel verantwoordelijkheid in één klasse is om 2 verschillende objecten te consumeren (als ze niet gekoppeld zijn).

Wat ik echter doe en wat u kunt doen, is op de volgende manier een expliciet fabrieksobject toevoegen om verbonden consumenten te creëren:

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

Als die typen in werkelijkheid echt gekoppeld (gerelateerd) zijn, dan zou ik aanraden om een ​​implementatie op een dergelijke manier te creëren:

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();
    }
}

Het voordeel is dat de fabrieksklasse beide implementaties kent, er een gedeelde status is (indien nodig) en dat u indien nodig meer gekoppelde consumenten kunt retourneren. Er is geen herhaalde declaratie van de verbruiksmethode die niet is afgeleid van de interface.

Houd er rekening mee dat elke consument een onafhankelijke (nog steeds privé) klasse kan zijn als ze niet volledig verwant zijn.

Het nadeel van die oplossing is een hogere klassecomplexiteit (zelfs als dit een één java-bestand kan zijn) en om toegang te krijgen tot de consumptiemethode heb je nog een aanroep nodig, dus in plaats van:

twoTypesConsumer.consume(apple)
twoTypesConsumer.consume(tomato)

je hebt:

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

Om samen te vatten: u kunt definiëren2 generieke consumenten in één klasse op het hoogste niveau met behulp van 2 interne klassen, maar in het geval van bellen moet u eerst een verwijzing krijgen naar de juiste implementatieconsument, aangezien dit niet zomaar een consumentenobject kan zijn.


Antwoord 8

Een ander alternatief om het gebruik van meer klassen te vermijden. (voorbeeld met 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);
}

Antwoord 9

Sorry voor het beantwoorden van oude vragen, maar Ik vind het echt geweldig!Probeer deze optie:

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

Volgens mij is dat wat u zoekt.

Je krijgt deze output:

Tomaat geconsumeerd!

Ik eet een appel

String verbruikt!

Other episodes