web-dev-qa-db-ja.com

2つのジェネリック型を持つ1つのインターフェイスを実装するJavaクラスを作成する方法は?

汎用インターフェイスがあります

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

2つのタイプのオブジェクトを消費するクラスがあるので、次のようなことをしたいと思います。

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

機能的には、この解決策は問題ないと思います。冗長でverbいだけです。

何か案は?

152
daphshez

カプセル化を検討してください:

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);
    }
}
71
Steve McLeod

型を消去するため、同じインターフェイスを(異なる型パラメーターで)2回実装することはできません。

38
Shimi Bandiel

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>オブジェクトの必要性は、これらをパラメーターとして期待する他のメソッドから発生します。状態を共有するために、両方を実装する1つのクラスが必要です。

スティーブのアイデアは、それぞれが異なるジェネリック型を実装する2つの内部クラスを使用することでした。

このバージョンでは、Consumerインターフェースを実装するオブジェクトにゲッターを追加し、それらを期待する他のメソッドに渡すことができます。

11
daphshez

少なくとも、次のようなことを行うことで、ディスパッチの実装を少し改善できます。

public class TwoTypesConsumer implements Consumer<Fruit> {

トマトとリンゴの祖先である果物。

7
Buhb

これにつまずいた。私は同じ問題を抱えていましたが、別の方法で解決しました:このような新しいインターフェイスを作成しました

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

残念ながら、これはConsumer<A>およびNOT Consumer<B>すべてのロジックに対して。したがって、クラス内でこのような2番目のコンシューマー用の小さなアダプターを作成する必要があります

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を渡すだけで必要です

3
Rafael T

以下のクラス定義は、ジェネリック型の消去と重複したインターフェイス宣言のためにコンパイルできないため、1つのクラスでこれを直接行うことはできません。

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

同じ消費操作を1つのクラスにパックする他のソリューションでは、クラスを次のように定義する必要があります。

class TwoTypesConsumer { ... }

両方の操作の定義を繰り返したり複製したりする必要があり、インターフェースから参照されないため、これは無意味です。これをやっている私見は、私が避けようとしている悪い小さなコードの重複です。

これは、1つのクラスで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();
    }
}

利点は、ファクトリクラスが両方の実装を認識し、共有状態(必要な場合)があり、必要に応じてより多くの結合コンシューマを返すことができることです。インターフェイスから派生していない繰り返しの消費メソッド宣言はありません。

完全に関連していない場合、各コンシューマは独立した(まだプライベートな)クラスである可能性があることに注意してください。

このソリューションの欠点は、クラスの複雑さが高いことです(これが1つのJavaファイル)である場合でも)、consumeメソッドにアクセスするには、次の代わりに1回の呼び出しが必要です。

twoTypesConsumer.consume(Apple)
twoTypesConsumer.consume(tomato)

あなたが持っている:

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

まとめるとdefine 2つの内部クラスを使用する1つのトップレベルクラスで2つの汎用コンシューマーを呼び出すことができますが、呼び出す場合はまず適切なimplementingコンシューマーへの参照を取得する必要があります単に1つのコンシューマオブジェクトにすることはできません。

1
kitarek

古い質問に答えてすみませんが、本当に気に入っています!このオプションを試してください:

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

それがあなたが探しているものだと思います。

次の出力が得られます。

トマトが消費されました!

私はリンゴを食べる

ストリングが消費されました!

0
Awes0meM4n

より多くのクラスの使用を回避する別の代替手段。 (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);
}
0
winter