4

私はいつも、Javaジェネリックの奇妙な側面とワイルドカードの使用について疑問に思っていました。たとえば、次のAPIがあります。

public interface X<E> {
    E get();
    E set(E e);
}

次に、次のメソッドを宣言するとします。

public class Foo {
    public void foo(X<?> x) {
        // This does not compile:
        x.set(x.get());
    }

    public <T> void bar(X<T> x) {
        // This compiles:
        x.set(x.get());
    }
}

私の「直感的な」理解から、未知のものがクライアントコードに正式に未知であるということを除いて、X<?>実際にはと同じです。しかし、の内部では、コンパイラーが(疑似JLSコード)を推測できると思います。これは、ほとんどのプログラマーが明示的かつ直感的に行うことです。彼らはプライベートヘルパーメソッドに委任します。これは多くの役に立たないボイラープレートコードにつながります。X<T><T>foo()<T0> := <?>[0], <T1> := <?>[1], etc...

public class Foo {
    public void foo(X<?> x) {
        foo0(x);
    }

    private <T> void foo0(X<T> x) {
        x.set(x.get());
    }
}

もう一つの例:

public class Foo {
    public void foo() {
        // Assuming I could instanciate X
        X<?> x = new X<Object>();
        // Here, I cannot simply add a generic type to foo():
        // I have no choice but to introduce a useless foo0() helper method
        x.set(x.get());
    }
}

つまり、コンパイラは、のワイルドカードx.set()が形式的にはのワイルドカードと同じであることを認識していx.get()ます。なぜその情報を使用できないのですか?JLSには、この不足しているコンパイラの「機能」を説明する形式的な側面がありますか?

4

3 に答える 3

5

なぜサポートされていないのだろうか?同様に有効な質問は、なぜそれをサポートする必要があるのか​​ということです。

ワイルドカードが機能する方法は、宣言が与えられたX<? extends Y> x場合、式の型はコンパイラによって推測された型でx.get()はなく、または何らかの型であると推測されます。現在、はに割り当てることができないため、の有効な署名を持つに渡すことはできません。実際に渡すことができるのは、すべての参照型に割り当て可能な唯一のものです。? extends YYY? extends Yx.get()x.setvoid set(? extends Y e)null

私はJDK開発者のメーリングリストにかなり厳密に従っていますが、JLSが指定するすべての機能はそれ自体の重みを引き出す必要があるという一般的な精神があります。つまり、費用便益比は利益面で大きくなければなりません。これで、値の原点を追跡することは非常に可能ですが、それを解決する方法が他にいくつかある1つの特殊なケースしか解決できません。次のクラスを検討してください。

class ClientOfX {
    X<?> member;
    void a(X<?> param) {
        param.set(param.get());
    }
    void b() {
        X<?> local = new XImpl<Object>();
        local.set(local.get());
    }
    void c() {
        member.set(member.get());
    }
    void d() {
        ClassWeCantChange.x.set(ClassWeCantChange.x.get());
    }
}

class ClassWeCantChange {
    public static X<?> x;
}

これは明らかにコンパイルされませんが、提案された拡張機能を使用すると、コンパイルされます。ただし、JLSを変更せずに、4つのメソッドのうち3つをコンパイルできます。

  1. の場合a、ジェネリックオブジェクトをパラメーターとして受け取ると、メソッドをジェネリックにして、のX<T>代わりに受け取ることができますX<?>
  2. bおよび、ローカルで宣言された参照を使用する場合c、ワイルドカードを使用して参照を宣言する必要はありません( MicrosoftのC#コンパイラチームのEric Lippertは、この状況を「それを行うときに痛い場合は、それを行わないでください!」と呼びます。 )。
  3. の場合d、宣言を制御できない参照を使用する場合、ヘルパーメソッドを作成する以外に選択肢はありません。

したがって、よりスマートな型システムは、変数の宣言を制御できない場合にのみ実際に役立ちます。そしてその状況では、あなたがやりたいことをする場合は一種の大ざっぱです-ジェネリック型をワイルドカード化するという行為は通常、オブジェクトがオブジェクトのプロデューサー(? extends Something)またはコンシューマー(? super Something)としてのみ機能することを意図していることを意味します、両方ではありません。

TL; DRバージョンでは、JLSに複雑さを加えながら、ほとんどメリットがありません(そして、ワイルドカードは現在のものとは別のものである必要があります!)。誰かがそれを追加するための非常に説得力のあるケースを思いついた場合、仕様を拡張することができます-しかし、一度そこに入ると、それは永遠にそこにあり、将来予期しない問題を引き起こす可能性があるので、本当に必要になるまでそれを省いてください。

編集:コメントには、ワイルドカードとは何か、そうでないものについての詳細な説明が含まれています。

于 2012-07-16T09:04:28.157 に答える
2

私はちょうどこの独特の実装を見ましたCollections.swap()

public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

JDKの人たちは、これをローカルで処理するために、rawタイプに頼っています。私にとって、これはおそらくコンパイラによってサポートされるべきであることを示す強力なステートメントですが、何らかの理由(たとえば、これを正式に指定する時間の不足)が行われなかっただけです

于 2012-08-16T07:32:51.063 に答える
1

JLSの作成に関与していなかったので、私の最も良い推測は単純さです。ばかげているが(構文的に)有効な式(。など)を想像してみてください(x = new X<Integer>()).set(x.get());。ワイルドカードのキャプチャを追跡するのは難しい場合があります。

ワイルドカードをキャプチャすることは、定型文としてではなく、何か正しいことをしたと思います。これは、APIにクライアントコードが必要としない型パラメーターがないことを示しています。

編集:現在の動作はJLS§4.5.2で指定されています。はx、メンバーアクセスごとにキャプチャ変換されx.set(...)ますx.get()。キャプチャは別個のものであるため、同じタイプを表すことはわかりません。

于 2012-07-16T09:00:35.460 に答える