【问题标题】:How to make a Java class that implements one interface with two generic types?如何制作一个实现一个接口和两个泛型类型的 Java 类?
【发布时间】:2010-11-20 20:14:25
【问题描述】:

我有一个通用接口

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

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

有什么想法吗?

【问题讨论】:

  • 为什么需要两个相同基类型的泛型接口?
  • 由于类型擦除,您不能这样做。保留两个实现消费者的不同类。制作更多小类,但保持代码通用(不要使用公认的答案,它会破坏整个概念......你不能将 TwoTypesConsumer 视为消费者,这是不好的)。
  • 检查此功能样式 impl - stackoverflow.com/a/60466413/4121845

标签: java generics interface multiple-inheritance


【解决方案1】:

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

【讨论】:

  • 我知道这是个问题...问题是什么是绕过这个问题的最佳(最有效、最安全、最优雅)的方法。
  • 没有进入业务逻辑,这里的东西“闻起来”像访问者模式。
【解决方案2】:

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

public class TwoTypesConsumer implements Consumer<Fruit> {

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

【讨论】:

  • 谢谢,但无论专业人士怎么说,我都不认为番茄是水果。不幸的是,除了 Object 之外没有通用的基类。
  • 您总是可以创建一个名为:AppleOrTomato 的基类;)
  • 更好,添加一个 Fruit 代表 Apple 或 Tomato。
  • @Tom:除非我误解了你的意思,否则你的建议只会推动问题向前发展,因为为了让 Fruit 能够委托给 Apple 或 Tomato,Fruit 必须有一个字段Apple 和 Tomato 的超类,指代它委托给的对象。
  • 这意味着 TwoTypesConsumer 可以消费任何类型的 Fruit,任何当前实现的以及将来可能实现的任何人。
【解决方案3】:

考虑封装:

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

【讨论】:

  • 有点像代码重复...我遇到了同样的问题,但没有找到其他看起来干净的解决方案。
  • 但是TwoTypesConsumer 履行了no 合同,那有什么意义呢?它不能传递给需要Consumer 中的任何一种类型的方法。两种类型的消费者的整个想法是,您可以将其提供给需要番茄消费者的方法以及需要苹果消费者的方法。在这里我们都没有。
  • @JeffAxelrod 我将使内部类成为非静态类,以便它们在必要时可以访问封闭的TwoTypesConsumer 实例,然后您可以将twoTypesConsumer.getAppleConsumer() 传递给需要苹果消费者的方法。另一种选择是将类似于 addConsumer(Producer&lt;Apple&gt; producer) 的方法添加到 TwoTypesConsumer。
  • 如果您无法控制界面(例如 cxf/rs ExceptionMapper),这将不起作用 ...
  • 我会说:这是 Java 的一个缺陷。绝对没有理由不允许我们拥有同一个接口的多个实现,只要这些实现采用不同的参数。
【解决方案4】:

这是一个基于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&lt;Tomato&gt;Consumer&lt;Apple&gt; 对象。对Consumer&lt;Tomato&gt;, Consumer&lt;Apple&gt; 对象的需求来自其他期望这些作为参数的方法。我需要一个类来实现它们以共享状态。

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

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

【讨论】:

  • 如果有人使用它:如果经常调用 get*Consumer,则值得将 Consumer&lt;*&gt; 实例存储在实例字段中。
【解决方案5】:

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

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

不幸的是,这被视为Consumer&lt;A&gt; 而不是Consumer&lt;B&gt; 反对所有逻辑。所以你必须在你的类中为第二个消费者创建一个小适配器

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&lt;A&gt;,您可以简单地传递this,如果需要Consumer&lt;B&gt;,只需传递consumerAdapter

【讨论】:

  • Daphna 的答案是一样的,但更简洁,更简洁。
【解决方案6】:

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

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 个通用消费者,但在调用时,您需要首先获得对适当实现的引用消费者,因为这不能只是一个消费者对象。

【讨论】:

    【解决方案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));
        }
      }
      

      我想这就是你要找的。​​p>

      你得到这个输出:

      番茄吃完了!

      我吃了一个苹果

      字符串已消耗!

      【讨论】:

      • 有问题:“但我正在寻找编译时类型检查...”
      • @aeracode 没有选项可以做 OP 想要的。类型擦除使得使用不同类型变量两次实现相同接口是不可能的。我只是试图给你另一种方式。当然,您可以检查之前接受的类型以使用 onbject。
      【解决方案9】:

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

      我们消费实体的功能接口

      @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-11-28
        • 2018-07-23
        • 2021-10-09
        • 2021-05-05
        • 2010-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多