web-dev-qa-db-ja.com

ジェネリックスで参照があいまいです

私はここでジェネリックスとメソッドのオーバーロードに関してかなりトリッキーなケースを抱えています。このサンプルクラスを確認してください。

public class Test {
    public <T> void setValue(Parameter<T> parameter, T value) {
    }

    public <T> void setValue(Parameter<T> parameter, Field<T> value) {
    }

    public void test() {
        // This works perfectly. <T> is bound to String
        // ambiguity between setValue(.., String) and setValue(.., Field)
        // is impossible as String and Field are incompatible
        Parameter<String> p1 = getP1();
        Field<String> f1 = getF1();
        setValue(p1, f1);

        // This causes issues. <T> is bound to Object
        // ambiguity between setValue(.., Object) and setValue(.., Field)
        // is possible as Object and Field are compatible
        Parameter<Object> p2 = getP2();
        Field<Object> f2 = getF2();
        setValue(p2, f2);
    }

    private Parameter<String> getP1() {...}
    private Parameter<Object> getP2() {...}

    private Field<String> getF1() {...}
    private Field<Object> getF2() {...}
}

上記の例はEclipse(Java 1.6)で完全にコンパイルされますが、Ant javacコマンド(またはJDKのjavacコマンド)ではコンパイルされません。setValueの2回目の呼び出しでこの種のエラーメッセージが表示されます。

setValueへの参照があいまいであり、テストのメソッドsetValue(org.jooq.Parameter、T)とテストのメソッドsetValue(org.jooq.Parameter、org.jooq.Field)の両方が一致しています

仕様と、Javaコンパイラがどのように機能するかについての私の理解によれば、最も具体的な方法を常に選択する必要があります: http://Java.Sun.com/docs/books /jls/third_edition/html/expressions.html#20448

いずれにせよ、<T>Objectにバインドされていて、両方のsetValueメソッドが呼び出しの候補として受け入れられる場合でも、Fieldパラメーターを持つメソッドは常により具体的であるように見えます。また、JDKのコンパイラではなく、Eclipseで機能します。

[〜#〜]更新[〜#〜]

このように、EclipseとJDKコンパイラの両方で機能します(もちろん、rawtypes警告付き)。 仕様 で指定されたルールは、ジェネリックが含まれる場合、非常に特殊であることを理解しています。しかし、これはかなり紛らわしいと思います。

    public <T> void setValue(Parameter<T> parameter, Object value) {
    }

    // Here, it's easy to see that this method is more specific
    public <T> void setValue(Parameter<T> parameter, Field value) {
    }

UPDATE 2

ジェネリックスを使用する場合でも、<T>と呼ばれる明確な間接参照を追加することで、Object呼び出し時にタイプsetValue0setValueにバインドされないようにするこの回避策を作成できます。これにより、TObjectへのバインドが、ここですべての問題を引き起こしているのだと思います。

    public <T> void setValue(Parameter<T> parameter, T value) {
    }

    public <T> void setValue(Parameter<T> parameter, Field<T> value) {
    }

    public <T> void setValue0(Parameter<T> parameter, Field<T> value) {
        // This call wasn't ambiguous in Java 7
        // It is now ambiguous in Java 8!
        setValue(parameter, value);
    }

    public void test() {
        Parameter<Object> p2 = p2();
        Field<Object> f2 = f2();
        setValue0(p2, f2);
    }

私はここで何かを誤解していますか?これに関連する既知のコンパイラバグはありますか?または、私を助けるための回避策/コンパイラ設定はありますか?

ファローアップ:

興味のある方のために、OracleとEclipseの両方にバグレポートを提出しました。 Oracleはバグを受け入れましたが、これまでのところ、Eclipseはバグを分析し、拒否しました。私の直感は正しいように見えますが、これはjavacのバグです

37
Lukas Eder

JDKは正しいです。 2番目の方法は、1番目の方法よりも具体的ではありません。 JLS3#15.12.2.5から

「非公式の直感では、最初のメソッドで処理された任意の呼び出しがコンパイル時の型エラーなしで他のメソッドに渡される場合、1つのメソッドは別のメソッドよりも具体的です。」

これは明らかにここでは当てはまりません。私は強調しました任意の呼び出し。一方のメソッドが他方よりも具体的であるという特性は、純粋に2つのメソッド自体に依存します。呼び出しごとに変更されません。

あなたの問題に関する正式な分析:m2はm1よりも具体的ですか?

m1: <R> void setValue(Parameter<R> parameter, R value) 
m2: <V> void setValue(Parameter<V> parameter, Field<V> value) 

まず、コンパイラは初期制約からRを推測する必要があります。

Parameter<V>   <<   Parameter<R>
Field<V>       <<   R

結果は、15.12.2.7の推論規則に従ってR=Vになります。

次に、Rを置き換えて、サブタイプの関係を確認します

Parameter<V>   <:   Parameter<V>
Field<V>       <:   V

4.10.2のサブタイプ規則に従って、2行目は成り立ちません。したがって、m2はm1よりも具体的ではありません。

この分析では、VObjectではありません。分析では、Vのすべての可能な値が考慮されます。

別のメソッド名を使用することをお勧めします。オーバーロードは決して必要ではありません。


これはEclipseの重大なバグのようです。仕様は、型変数がこのステップで置換されないことを非常に明確に示しています。 Eclipseは明らかに最初に型変数置換を行い、次にメソッドの特異性の関係を確認します。

いくつかの例でそのような振る舞いがより「賢明」である場合、他の例ではそうではありません。いう、

m1: <T extends Object> void check(List<T> list, T obj) { print("1"); }
m2: <T extends Number> void check(List<T> list, T num) { print("2"); }

void test()
    check( new ArrayList<Integer>(), new Integer(0) );

「直感的に」、正式には仕様ごとに、m2はm1よりも具体的であり、テストでは「2」が出力されます。ただし、最初にT=Integerを代入すると、2つの方法は同じになります。


アップデート2の場合

m1: <R> void setValue(Parameter<R> parameter, R value) 
m2: <V> void setValue(Parameter<V> parameter, Field<V> value) 

m3: <T> void setValue2(Parameter<T> parameter, Field<T> value)
s4:             setValue(parameter, value)

ここで、m1はメソッド呼び出しs4には適用できないため、m2が唯一の選択肢です。

15.12.2.2に従って、m1がs4に適用可能かどうかを確認するために、最初に型推論が実行され、R = Tであるという結論に達します。次に、Ai :< Siをチェックします。これは、falseであるField<T> <: Tにつながります。

これは前の分析と一致しています-m1がs4に適用できる場合、m2によって処理されるすべての呼び出し(本質的にs4と同じ)はm1によって処理できます。つまり、m2はm1よりも具体的であり、これは誤りです。

パラメータ化されたタイプで

次のコードを検討してください

class PF<T>
{
    public void setValue(Parameter<T> parameter, T value) {
    }

    public void setValue(Parameter<T> parameter, Field<T> value) {
    }
}

void test()

    PF<Object> pf2 = null;

    Parameter<Object> p2 = getP2();
    Field<Object> f2 = getF2();

    pf2.setValue(p2,f2);

これは問題なくコンパイルされます。 4.5.2によると、PF<Object>のメソッドのタイプは、PF<T>のメソッドであり、置換はT=Objectです。つまり、pf2のメソッドは次のとおりです。

    public void setValue(Parameter<Object> parameter, Object value) 

    public void setValue(Parameter<Object> parameter, Field<Object> value) 

2番目の方法は1番目の方法よりも具体的です。

24
irreputable

私の推測では、コンパイラは JLS、セクション15.12.2.5 に従って解決をオーバーロードするメソッドを実行しています。

このセクションでは、コンパイラは strongサブタイピング (したがって、チェックされていない変換を許可しない)を使用するため、T valueObject valueになり、Field<T> valueField<Object> valueになります。次のルールが適用されます。

メソッドmは、次の両方の条件が当てはまる場合にのみ、サブタイプ化することで適用できます。

* For 1in, either:
      o Ai is a subtype (§4.10) of Si (Ai <: Si) or
      o Ai is convertible to some type *Ci* by unchecked conversion

(§5.1.9)、およびCi <:Si。 * mが上記のようなジェネリックメソッドの場合、Ul <:Bl [R1 = U1、...、Rp = Up]、1lp。

(箇条書き2を参照)。 Field<Object>Objectのサブタイプであるため、最も具体的なメソッドが見つかります。フィールドf2は、両方の方法に一致し(上記の箇条書き2のため)、あいまいになります。

StringField<String>の場合、2つの間にサブタイプの関係はありません。

PS。これは私の理解です。コーシャとして引用しないでください。

0
Buhake Sindi