18

Java Generics FAQ のこのエントリによると、ジェネリック メソッドに、ワイルドカード型を使用する同等の非ジェネリック メソッドがない場合があります。その答えによると、

メソッド シグネチャが複数レベルのワイルドカード タイプを使用する場合、ジェネリック メソッド シグネチャとそのワイルドカード バージョンの間には常に違いがあります。

彼らは、<T> void print1( List <Box<T>> list)「同じタイプのボックスのリストを必要とする」メソッドの例を示しています。ワイルドカード バージョン はvoid print2( List <Box<?>> list)、「さまざまな種類のボックスの異種リストを受け入れる」ため、同等ではありません。

次の 2 つのメソッド シグネチャの違いをどのように解釈しますか。

 <T extends Iterable<?>> void f(Class<T> x) {}
                         void g(Class<? extends Iterable<?>> x) {}

直感的には、これらの定義は同等であるように思われます。ただし、呼び出しf(ArrayList.class)は最初の方法を使用してg(ArrayList.class)コンパイルされますが、2 番目の方法を使用した呼び出しはコンパイル時エラーになります。

g(java.lang.Class<? extends java.lang.Iterable<?>>) in Test
    cannot be applied to (java.lang.Class<java.util.ArrayList>)

興味深いことに、次のようにコンパイルされるため、両方の関数を互いの引数で呼び出すことができます。

class Test {
    <T extends Iterable<?>> void f(Class<T> x) {
        g(x);
    }
    void g(Class<? extends Iterable<?>> x) {
        f(x);
    }
}

を使用すると、一般的な署名があるjavap -verbose Testことがわかりますf()

<T::Ljava/lang/Iterable<*>;>(Ljava/lang/Class<TT;>;)V;

およびg()一般的な署名を持っています

(Ljava/lang/Class<+Ljava/lang/Iterable<*>;>;)V;

この動作を説明するものは何ですか? これらのメソッドのシグネチャの違いをどのように解釈すればよいですか?

4

4 に答える 4

4

まあ、仕様で言えば、どちらの呼び出しも合法ではありません。しかし、最初のタイプはチェックするのに、2 番目のタイプはチェックしないのはなぜでしょうか?

違いは、メソッドの適用可能性をチェックする方法にあります (特に、 §15.12.2および§15.12.2.2を参照してください)。

  • 単純な非ジェネリックgを適用するには、引数Class<ArrayList>が のサブタイプである必要がありClass<? extends Iterable<?>>ます。つまり、書かれた を含む? extends Iterable<?>必要があります。ルール41は推移的に適用できるため、 のサブタイプである必要があります。 ArrayListArrayList <= ? extends Iterable<?>ArrayListIterable<?>

    §4.10.2 によると、すべてのパラメータ化C<...>は raw type の (直接) サブタイプCです。ArrayList<?>のサブタイプもそうですがArrayList、その逆ではありません。推移的に、ArrayListのサブタイプではありませんIterable<?>

    したがってg、該当しません。

  • f簡単にするために、型引数ArrayListが明示的に指定されていると仮定しましょう。f適用可能性をテストするClass<ArrayList>には、 のサブタイプである必要がありClass<T> [T=ArrayList] = Class<ArrayList>ます。サブタイピングはreflexisveであるため、それは真です。

    また、f適用可能であるためには、型引数がその境界内にある必要があります。上で示したように、ArrayListが のサブタイプではないからではありませんIterable<?>

では、なぜそれはとにかくコンパイルされるのでしょうか?

バグです。バグ レポートその後の修正に従って、JDT コンパイラは最初のケース (型引数の包含) を明示的に除外します。JDT は( )ArrayListのサブタイプであると見なすため、2 番目のケースは喜んで無視されます。Iterable<?>TypeBinding.isCompatibleWith(TypeBinding)

javac が同じように動作する理由はわかりませんが、同様の理由であると思います。ArrayListrawをIterable<?>eitherに代入するときに、javac が unchecked 警告を発行しないことに気付くでしょう。

于 2012-07-20T21:42:18.377 に答える
3

型パラメーターがワイルドカードでパラメーター化された型である場合、問題は発生しません。

Class<ArrayList<?>> foo = null;
f(foo);
g(foo);

これはほぼ間違いなく、クラス リテラルの型が であるという事実から生じる奇妙なケースだと思います。Class<ArrayList>したがって、この場合の型パラメーター ( ) は raw 型であり、rawとワイルドカード パラメーター化ArrayListの間のサブタイプ関係は複雑です。 .ArrayListArrayList<?>

私は言語仕様をよく読んでいないので、明示的な型パラメーターの場合はサブタイプが機能するのに、ワイルドカードの場合は機能しない理由が正確にはわかりません。また、バグである可能性も十分にあります。

于 2012-07-20T02:52:18.803 に答える
0

これらはまったく同じではありません。

<T extends Iterable<?>> void f(Class<T> x) {}
void g(Class<? extends Iterable<?>> x) {}

違いはg、「未知の Iterable を実装する未知のクラス」を受け入れるが、ではなくをArrayList<T>実装するように制約されているため、一致しないことです。Iterable<T>Iterable<?>

明確にするために、 は を受け入れますが、gは受け入れFoo implements Iterable<?>ませんAraryList<T> implements Iterable<T>

于 2012-07-20T03:08:16.830 に答える
0

推測: 最初の ? (ArrayList) は「実装」しませんArrayList<E>(二重にネストされたワイルドカードのおかげで)。私はこれがおかしいと思いますが....

考慮してください(元のリストの場合):

 void g(Class<? extends Iterable<Object> x) {} // Fail
 void g(Class<? extends Iterable<?> x) {}  // Fail
 void g(Class<? extends Iterable x) {}  // OK

// Compiles
public class Test{
    <T extends Iterable<?>> void f(ArrayList<T> x) {}
    void g(ArrayList<? extends Iterable<?>> x) {}

    void d(){
        ArrayList<ArrayList<Integer>> d = new ArrayList<ArrayList<Integer>>();
        f(d);
        g(d);
    }
}

これ

// Does not compile on g(d)
public class Test{
    <T extends Iterable<?>> void f(ArrayList<T> x) {}
    void g(ArrayList<? extends Iterable<?>> x) {}

    void d(){
        ArrayList<ArrayList> d = new ArrayList<ArrayList>();
        f(d);
        g(d);
    }
}
于 2012-07-19T23:07:55.690 に答える