175

我有一个通用接口

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

从功能上讲,我认为这个解决方案还可以。它只是冗长而丑陋。

有任何想法吗?

4

9 回答 9

79

考虑封装:

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);
    }
}
于 2009-08-19T07:43:38.353 回答
43

由于类型擦除,您不能两次实现相同的接口(使用不同的类型参数)。

于 2009-08-19T05:48:57.100 回答
12

这是基于史蒂夫麦克劳德的一个可能的解决方案:

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>对象的需求来自期望这些作为参数的其他方法。我需要一个类来实现它们以共享状态。

Steve 的想法是使用两个内部类,每个实现不同的泛型类型。

此版本为实现 Consumer 接口的对象添加了 getter,然后可以将其传递给期望它们的其他方法。

于 2009-08-19T09:27:38.500 回答
7

至少,您可以通过执行以下操作对调度的实现进行小幅改进:

public class TwoTypesConsumer implements Consumer<Fruit> {

水果是番茄和苹果的祖先。

于 2009-08-19T06:13:14.260 回答
3

只是偶然发现了这一点。只是发生了,我遇到了同样的问题,但我用不同的方式解决了它:我刚刚创建了一个这样的新界面

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>需要 a ,您可以简单地通过this,如果Consumer<B>需要,只需通过consumerAdapter

于 2012-09-22T23:21:16.240 回答
3

在函数式风格中,无需实现接口就很容易做到这一点,而且它还进行编译时类型检查。

我们消费实体的功能接口

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

我们的经理适当地处理和使用实体

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());
    }
}
于 2020-02-29T14:58:41.943 回答
2

您不能直接在一个类中执行此操作,因为由于泛型类型的擦除和重复的接口声明,无法编译下面的类定义。

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 文件)并且要访问 consume 方法,您需要再调用一次,而不是:

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

你有:

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

总而言之,您可以使用 2 个内部类在一个顶级类中定义2 个通用消费者,但在调用的情况下,您需要首先获得对适当实现消费者的引用,因为这不能只是一个消费者对象。

于 2016-08-29T09:06:32.780 回答
0

避免使用更多类的另一种选择。(使用 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);
}
于 2019-01-07T15:50:54.917 回答
0

很抱歉回答老问题,但我真的很喜欢它!试试这个选项:

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

我认为这就是你要找的。

你得到这个输出:

番茄吃完了!

我吃一个苹果

字符串消耗!

于 2019-01-18T17:41:17.280 回答