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


164

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

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

機能的には、このソリューションは問題ないと思います。冗長で見苦しいだけです。

何か案は?


同じベースタイプの2つの汎用インターフェースが必要なのはなぜですか?
akarnokd

6
タイプ消去のため、それはできません。コンシューマーを実装する2つの異なるクラスを保持します。より小さなクラスを作成しますが、コードを汎用に保ちます(受け入れられた回答を使用しないでください。コンセプト全体が壊れます... TwoTypesConsumerをコンシューマーとして扱うことはできません。これは悪いことです)。
ルイスダイヤモンド

機能的なスタイルのimplのためにこれをチェック- stackoverflow.com/a/60466413/4121845
mano_ksp

回答:


78

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

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

2
どういうわけかそれはコードの重複のように見えます...私は同じ問題に遭遇し、きれいに見える他の解決策を見つけませんでした。
bln-tom

109
しかし契約TwoTypesConsumerを履行しないので、ポイントは何ですか?いずれかのタイプを必要とするメソッドに渡すことはできませんConsumer。2つのタイプのコンシューマーの全体的なアイデアは、トマトのコンシューマーを必要とするメソッドだけでなく、アップルのコンシューマーを必要とするメソッドにも与えることができるということです。ここにはどちらもありません。
ジェフアクセルロッド

@JeffAxelrod内部クラスを非静的にしてTwoTypesConsumer、必要に応じてそれらが囲んでいるインスタンスにアクセスできるようにします。その後twoTypesConsumer.getAppleConsumer()、アップルコンシューマを必要とするメソッドに渡すことができます。別のオプションはaddConsumer(Producer<Apple> producer)、TwoTypesConsumer と同様のメソッドを追加することです。
ハーマン2012

これは、インターフェース(cxf / rsなどExceptionMapper)を制御できない場合は機能しません...
vikingsteve

17
私はそれを言います:これはJavaの欠陥です。実装が異なる引数をとる場合、同じインターフェースの複数の実装が許可されるべきではない理由は絶対にありません。
gromit190

41

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


6
私はそれがどのように問題であるかを見ることができます...問題は、この問題を回避するための最良(最も効率的、安全、エレガント)な方法です。
daphshez 2009

2
ここでは、ビジネスロジックを使わずに、Visitorパターンのような「におい」を感じます。
Shimi Bandiel、2009

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

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

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


2
誰かがこれを使用する場合:頻繁に呼び出されるConsumer<*>場合、インスタンスをインスタンスフィールドに格納する価値がありますget*Consumer
TWiStErRob 2015

7

少なくとも、次のようなことを行うことで、ディスパッチの実装に小さな改善を加えることができます。

public class TwoTypesConsumer implements Consumer<Fruit> {

フルーツはトマトとアップルの祖先です。


14
ありがとう、でもプロが言っていることは何でも、私はトマトを果物とは見なしていません。残念ながら、Object以外に共通の基本クラスはありません。
daphshez 2009

2
AppleOrTomatoと呼ばれる基本クラスはいつでも作成できます;)
Shimi Bandiel 09

1
AppleかTomatoのどちらかにデリゲートするフルーツを追加してください。
トム・ホーティン-09

@Tom:あなたが言っていることを誤解していない限り、FruitがAppleまたはTomatoのいずれかに委任できるようにするには、FruitがAppleとTomatoの両方にスーパークラスのフィールドを持っている必要があるため委任先のオブジェクトを参照します。
Buhb、2009

1
これは、TwoTypesConsumerが任意のタイプのFruit、現在実装されている果物、および将来実装される可能性のある果物を消費できることを意味します。
トム・ギレン

3

ちょうどこれにつまずいた。たまたま、同じ問題が発生しましたが、別の方法で解決しました。このような新しいインターフェースを作成しただけです。

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

残念ながら、これはすべてのロジックに対するものではなく、すべてのロジックに対するものではConsumer<A>ありません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){ //...
    }
}

a Consumer<A>が必要な場合は単に渡すことができthisConsumer<B>必要な場合は単に渡すconsumerAdapter


Daphnaの答えは同じですが、より簡潔で複雑ではありません。
TWiStErRob 2015

1

ジェネリック型の消去とインターフェイス宣言の重複により、以下のクラス定義をコンパイルできないため、これを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ファイルである場合でも)で、消費メソッドにアクセスするには、次の代わりにもう1回呼び出す必要があります。

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

あなたが持っている:

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

要約すると、 2つの内部クラスを使用して1つの最上位クラスで2つの汎用コンシューマを定義できますが、呼び出しの場合は、適切な実装コンシューマへの参照を最初に取得する必要があります。これは単なる1つのコンシューマオブジェクトではないためです。


1

Functionalスタイルでは、インターフェースを実装せずにこれを行うのは非常に簡単です。また、コンパイル時の型チェックも行います。

エンティティーを消費するための機能インターフェース

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

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

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

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

次の出力が得られます。

トマト消費!

私はリンゴを食べます

文字列が消費されました!


質問:「しかし、コンパイル時の型チェックを探しています...」
aeracode

@aeracode OPが望むことを行うオプションはありません。型消去により、異なる型変数で同じインターフェイスを2回実装することが不可能になります。私はあなたに別の方法を与えるだけです。もちろん、以前に受け入れたタイプをチェックして、オブジェクトを消費することができます。
Awes0meM4n
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.