Как создать класс Java, который реализует один интерфейс с двумя универсальными типами?

У меня есть общий интерфейс

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

У меня есть класс, который потребляет два типа объектов, поэтому я хотел бы сделать что-то вроде:

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

По-видимому, я не могу этого сделать.

Я могу, конечно, сам осуществить отправку, например

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

Но я ищу решение для проверки и диспетчеризации времени компиляции, которое предоставляют дженерики.

Лучшее решение, о котором я могу думать, это определить отдельные интерфейсы, например

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

Функционально это решение в порядке, я думаю. Это просто многословный и уродливый.

Любые идеи?

Ответ 1

Рассмотрим инкапсуляцию:

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

Если создание этих статических внутренних классов вас беспокоит, вы можете использовать анонимные классы:

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

Ответ 2

Из-за стирания типа вы не можете реализовать один и тот же интерфейс дважды (с разными параметрами).

Ответ 3

Вот возможное решение, основанное на Стив Маклеод один:

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

Неявное требование вопроса состояло в Consumer<Tomato> и Consumer<Apple> объектах, разделяющих состояние. Потребность в объектах Consumer<Tomato>, Consumer<Apple> исходит из других методов, которые ожидают их как параметров. Мне нужен один класс, чтобы реализовать их как для совместного использования.

Идея Стива заключалась в том, чтобы использовать два внутренних класса, каждый из которых реализует другой общий тип.

В этой версии добавляются геттеры для объектов, реализующих пользовательский интерфейс, которые затем могут быть переданы другим методам, ожидающим их.

Ответ 4

По крайней мере, вы можете сделать небольшое улучшение для своей реализации отправки, выполнив следующее:

public class TwoTypesConsumer implements Consumer<Fruit> {

Фрукты, являющиеся предком Томатов и Яблока.

Ответ 5

Просто наткнулся на это. Случилось так, что у меня была одна и та же проблема, но я решил ее по-другому: Я просто создал новый интерфейс, подобный этому

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

К сожалению, это считается Consumer<A> и NOT как Consumer<B> для всей логики. Таким образом, вам нужно создать небольшой адаптер для второго потребителя, подобного этому внутри вашего класса.

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

если требуется a Consumer<A>, вы можете просто передать this, и если требуется Consumer<B>, просто пройдите consumerAdapter

Ответ 6

Вы не можете сделать это прямо в одном классе, поскольку определение класса ниже не может быть скомпилировано из-за стирания общих типов и декларации двойного интерфейса.

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

Любое другое решение для упаковки одних и тех же операций потребления в одном классе требует определения вашего класса следующим образом:

class TwoTypesConsumer { ... }

что бессмысленно, так как вам нужно повторить/дублировать определение обеих операций, и на них не будет ссылаться интерфейс. ИМХО делает это плохое маленькое дублирование кода, которое я пытаюсь избежать.

Это может быть также показателем того, что в одном классе слишком много ответственности, чтобы потреблять 2 разных объекта (если они не связаны).

Однако то, что я делаю, и что вы можете сделать, это добавить явный объект factory для создания подключенных пользователей следующим образом:

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

Если на самом деле эти типы действительно связаны (связаны), то я бы рекомендовал создать реализацию таким образом:

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

Преимущество состоит в том, что класс factory знает обе реализации, есть общее состояние (если необходимо), и при необходимости вы можете вернуть более связанных потребителей. Нет повторения декларации метода потребления, которые не выводятся из интерфейса.

Обратите внимание, что каждый потребитель может быть независимым (все еще закрытым) классом, если он не полностью связан.

Недостатком этого решения является сложность более высокого класса (даже если это может быть один Java файл), и для доступа к методу потребления вам нужен еще один вызов, а вместо:

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

у вас есть:

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

Подводя итог, вы можете определить 2 общих потребителя в одном классе верхнего уровня с использованием 2 внутренних классов, но в случае вызова вам нужно сначала получить ссылку на соответствующую реализацию потребитель, поскольку это не может быть просто одним потребительским объектом.

Ответ 7

Еще одна альтернатива, чтобы избежать использования большего количества классов. (пример с использованием 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);
}

Ответ 8

Извините за ответы на старые вопросы, но мне это очень нравится! Попробуйте эту опцию:

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

Я думаю, это то, что вы ищете.

Вы получаете этот вывод:

Томат потребляется!

Я ем яблоко

Строка израсходована!