web-dev-qa-db-ja.com

制限付きワイルドカードジェネリックに複数のインターフェイスを設定できないのはなぜですか?

Javaのジェネリック型には、直感に反するさまざまな種類のプロパティがあることを知っています。これは特に私が理解していないもので、誰かが私に説明してくれることを期待しています。クラスまたはインターフェースの型パラメーターを指定する場合、public class Foo<T extends InterfaceA & InterfaceB>を使用して複数のインターフェースを実装する必要があるように、それをバインドできます。ただし、実際のオブジェクトをインスタンス化している場合、これは機能しなくなります。 List<? extends InterfaceA>は問題ありませんが、List<? extends InterfaceA & InterfaceB>はコンパイルに失敗します。次の完全なスニペットを検討してください。

import Java.util.List;

public class Test {

  static interface A {
    public int getSomething();
  }

  static interface B {
    public int getSomethingElse();
  }

  static class AandB implements A, B {
    public int getSomething() { return 1; }
    public int getSomethingElse() { return 2; }
  }

  // Notice the multiple bounds here. This works.
  static class AandBList<T extends A & B> {
    List<T> list;

    public List<T> getList() { return list; }
  }

  public static void main(String [] args) {
    AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
    foo.getList().add(new AandB());
    List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
    // This last one fails to compile!
    List<? extends A & B> foobar = new LinkedList<AandB>();
  }
}

barのセマンティクスは明確に定義されている必要があります-1つだけではなく2つの型の共通部分を許可することによる型安全性の損失は考えられません。確かに説明があると思います。それが何か知っている人はいますか?

71
Adrian Petrescu

興味深いことに、インターフェースJava.lang.reflect.WildcardTypeは、ワイルドカード引数の上限と下限の両方をサポートしているようです。それぞれに複数の境界を含めることができます

Type[] getUpperBounds();
Type[] getLowerBounds();

これは、言語が許可するものをはるかに超えています。ソースコードに隠されたコメントがあります

// one or many? Up to language spec; currently only one, but this API
// allows for generalization.

インターフェイスの作成者は、これが偶発的な制限であると考えているようです。

あなたの質問に対する定型の答えは、ジェネリックはすでに複雑すぎるためです。複雑さを追加することは最後のわらになるかもしれません。

ワイルドカードに複数の上限を持たせるには、仕様全体をスキャンして、システム全体が引き続き機能することを確認する必要があります。

私が知っている1つの問題は型推論にあるでしょう。現在の推論規則は、単に交差タイプを処理することができません。制約を減らすルールはありませんA&B << C。に削減した場合

    A<<C 
  or
    A<<B

現在の推論エンジンは、このような分岐を可能にするために大幅な見直しが必要です。しかし、本当の深刻な問題は、これにより複数のソリューションが可能になることです。

ただし、推論はタイプセーフにとって必須ではありません。この場合、単純に推論を拒否して、プログラマーに型引数を明示的に入力するように依頼できます。したがって、推論の難しさは、交差のタイプに対する強力な議論ではありません。

35
irreputable

Java言語仕様 から:

4.9交差タイプ交差タイプはT1&...&Tn、n> 0の形式をとり、Ti、1inはタイプ式です。交差タイプは、キャプチャ変換(§5.1.10)およびタイプ推論(§15.12.2.7)のプロセスで発生します。 交差点タイプをプログラムの一部として直接記述することはできません。これをサポートする構文はありません。交差タイプの値は、1インチのすべてのタイプTiの値であるオブジェクトです。

では、なぜこれがサポートされていないのですか?私の推測では、あなたはそのようなことで何をすべきですか? -それが可能であったとしましょう:

List<? extends A & B> list = ...

次に何をすべきか

list.get(0);

戻る? A & Bの戻り値をキャプチャする構文はありません。そのようなリストに何かを追加することも不可能であるため、基本的には役に立ちません。

24
emboss

問題ありません...メソッドシグネチャで必要な型を宣言してください。

これはコンパイルします:

public static <T extends A & B> void main(String[] args) throws Exception
{
    AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
    foo.getList().add(new AandB());
    List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
    List<T> foobar = new LinkedList<T>(); // This compiles!
}
10
Bohemian

良い質問。理解するのに少し時間がかかりました。

あなたのケースを簡単にしましょう:2つのインターフェースを拡張するクラスを宣言し、次に2つのインターフェースを型として持つ変数を宣言する場合と同じように、次のようにします。

  class MyClass implements Int1, Int2 { }

  Int1 & Int2 variable = new MyClass()

もちろん違法です。そして、これはジェネリックでやろうとしていることと同じです。あなたがやろうとしているのは:

  List<? extends A & B> foobar;

しかし、foobarを使用するには両方のインターフェイスの変数を使用する必要がありますこのように:

  A & B element = foobar.get(0);

非合法はJavaです。これは、リストの要素を2つのタイプのbeeingとして同時に宣言していることを意味します。脳がそれを処理できる場合でも、Java言語はできません。

1
Mr.Eddart

それが価値あるもののために:実際にこれを実際に使用したいので誰かがこれを疑問に思っている場合は、私が取り組んでいるすべてのインターフェイスとクラスのすべてのメソッドの結合を含むインターフェイスを定義することで、これを回避しました。つまり、私は次のことを試みていました:

class A {}

interface B {}

List<? extends A & B> list;

これは違法です-代わりに私はこれをしました:

class A {
  <A methods>
}

interface B {
  <B methods>
}

interface C {
  <A methods>
  <B methods>
}

List<C> list;

これは、List<? extends A implements B>のように何かを入力できるほど便利ではありません。誰かがAまたはBにメソッドを追加または削除した場合、リストの入力は自動的に更新されず、Cを手動で変更する必要があります。

0
Mark