如何创建一个实现具有两个泛型类型的接口的Java类?

时间:2009-08-19 05:43:46

标签: java generics interface multiple-inheritance

我有一个通用界面

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

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

有什么想法吗?

9 个答案:

答案 0 :(得分:70)

考虑封装:

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

答案 1 :(得分:36)

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

答案 2 :(得分:9)

以下是基于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);
            }
        }
    }
}

问题的隐含要求是共享状态的Consumer<Tomato>Consumer<Apple>个对象。对Consumer<Tomato>, Consumer<Apple>个对象的需求来自于将这些作为参数的其他方法。我需要一个类来实现它们以便共享状态。

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

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

答案 3 :(得分:7)

至少,您可以通过执行以下操作来对您的调度实施做一点改进:

public class TwoTypesConsumer implements Consumer<Fruit> {

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

答案 4 :(得分: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>,您只需传递this,如果需要Consumer<B>则只需传递consumerAdapter

答案 5 :(得分:1)

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

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个普通消费者,但是在调用的情况下,您需要先获得对相应实施的引用消费者,因为这不能只是一个消费者对象。

答案 6 :(得分:1)

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

我们使用实体的功能接口

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

答案 7 :(得分: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);
}

答案 8 :(得分: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));
  }
}

我认为这就是您想要的。

您将获得以下输出:

  

番茄被消耗了!

     

我吃了一个苹果

     

已消耗字符串!