Как сделать класс 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);
}

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

Есть идеи?

 akarnokd19 авг. 2009 г., 10:16
Зачем вам два универсальных интерфейса одного базового типа?
 Lewis Diamond08 нояб. 2013 г., 17:40
Из-за стирания типа вы не можете этого сделать. Держите это двумя разными классами, которые реализует потребитель. Делает больше маленьких классов, но, к примеру, ваш код обобщенным (не используйте принятый ответ, это нарушает всю концепцию ... вы не можете рассматривать TwoTypesConsumer как потребителя, который является ПЛОХОЙ).

Ответы на вопрос(8)

так как приведенное ниже определение класса не может быть скомпилировано из-за стирания универсальных типов и дублирования объявления интерфейса.

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

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

class TwoTypesConsumer { ... }

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

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

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

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

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

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

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

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

у вас есть:

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

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

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

важды (с разными параметрами типа).

 daphshez19 авг. 2009 г., 10:59
Я вижу, как это проблема ... вопрос в том, какой самый лучший (самый эффективный, безопасный, элегантный) способ обойти эту проблему.
 Shimi Bandiel19 авг. 2009 г., 11:27
Не вдаваясь в бизнес-логику, что-то здесь «пахнет», как шаблон Visitor.

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

public class TwoTypesConsumer implements Consumer<Fruit> {

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

 Shimi Bandiel19 авг. 2009 г., 10:24
Вы всегда можете создать базовый класс с именем: AppleOrTomato;)
 Tom Hawtin - tackline19 авг. 2009 г., 18:40
Лучше, добавьте Фрукты, которые делегируют или Яблоку или Помидору.
 Buhb19 авг. 2009 г., 20:03
@Tom: Если я неправильно понимаю то, что вы говорите, ваше предложение только продвигает проблему вперед, поскольку для того, чтобы Fruit мог делегировать Apple или Tomato, у Fruit должно быть поле суперкласса как для Apple, так и для Tomato ссылаясь на объект, которому он делегирует.
 Tom Gillen05 мар. 2012 г., 12:07
Это будет означать, что TwoTypesConsumer может потреблять любой тип Fruit, любой реализованный в настоящее время, и любой кто-то может реализовать в будущем.
 daphshez19 авг. 2009 г., 09:46
Спасибо, но что бы ни говорили профессионалы, я не считаю помидор фруктом. К сожалению, нет общего базового класса, кроме Object.

ноМне действительно нравится это! Попробуйте эту опцию:

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

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

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

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

Я ем яблоко

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

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

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

к сожалению, это считаетсяConsumer<A> а не как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){ //...
    }
}

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

 TWiStErRob24 апр. 2015 г., 12:42
ДафнаОтвет тот же, но чище и менее запутанный.
Решение Вопроса

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

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);
    }
}
 bln-tom09 июн. 2011 г., 21:42
почему-то это выглядит как дублирование кода ... Я столкнулся с той же проблемой и не нашел другого решения, которое выглядит чисто.
 Jeff Axelrod11 авг. 2012 г., 05:31
НоTwoTypesConsumer отрабатываетнет контракты, так какой смысл? Это не может быть передано методу, который хочет любой типConsumer, Вся идея потребителя двух типов заключается в том, что вы можете передать его методу, который хочет потребителя томатов, а также методу, который хочет потребителя яблок. Здесь у нас нет ни того, ни другого.
 gromit19008 мая 2017 г., 13:24
Я скажу это: этоизъян с Java. Нет абсолютно никаких причин, по которым нам нельзя разрешать иметь несколько реализаций одного и того же интерфейса, при условии, что реализации принимают разные аргументы.
 vikingsteve12 нояб. 2013 г., 10:27
Это не работает, если у вас нет контроля над интерфейсом (например, cxf / rsExceptionMapper) ...
 herman27 сент. 2012 г., 13:04
@JeffAxelrod Я бы сделал внутренние классы нестатичными, чтобы они имели доступ к окружающимTwoTypesConsumer экземпляр при необходимости, а затем вы можете передатьtwoTypesConsumer.getAppleConsumer() к методу, который хочет потребителя яблок. Другой вариант - добавить методы, аналогичныеaddConsumer(Producer<Apple> producer) TwoTypesConsumer.

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

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> объекты приходят из других методов, которые ожидают их в качестве параметров. Мне нужен один класс, чтобы реализовать их оба, чтобы поделиться государством.

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

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

 TWiStErRob24 апр. 2015 г., 12:44
Если кто-то использует это: стоит хранитьConsumer<*> экземпляры в полях экземпляра, еслиget*Consumer называется часто.

Ваш ответ на вопрос