22

Javaオンラインチュートリアルを読んで、ワイルドカードキャプチャについて何も理解していません。例えば:

    import java.util.List;
    public class WildcardError {
     void foo(List<?> i) {
      i.set(0, i.get(0));
     }
    }

コンパイラが割り当てを安全に保持できないのはなぜですか?

たとえば、IntegerListを使用してメソッドを実行することによりi.getInteger値から取得することを認識しています。Integerそのため、インデックスの値を0同じ整数リスト( )に設定しようとしiます。

それで、何が問題なのですか?なぜワイルドカードヘルパーを書くのですか?

4

7 に答える 7

25

コンパイラが割り当てを安全に保持できないのはなぜですか?たとえば、整数リストを使用してメソッドを実行することにより、i.getから整数値を取得することがわかります。したがって、インデックス0の整数値を同じ整数リスト(i)に設定しようとします。

言い換えると、なぜコンパイラはワイルドカードタイプの2つの使用法List<?>

i.set(0, i.get(0));

同じ実際のタイプを参照しますか?

iそれには、式の両方の評価で同じインスタンスが含まれていることをコンパイラーが認識している必要があります。は最終的なものではないため、コンパイラは、2つの式の評価の間に割り当てられた可能性があるiかどうかを確認する必要があります。iこのような分析は、ローカル変数の場合にのみ簡単です(呼び出されたメソッドが特定のオブジェクトの特定のフィールドを更新するかどうかを誰が知っていますか?)。これは、めったに効果が現れないため、コンパイラーの複雑さがかなり増します。そのため、Javaプログラミング言語の設計者は、同じワイルドカードタイプのさまざまな使用法でさまざまなキャプチャを指定することにより、物事を単純に保ちました。

于 2012-08-20T19:59:20.297 に答える
23

コンパイラが割り当てを安全に保持できないのはなぜですか?

コンパイラは、の定義により、の要素のタイプについて何も知りません。ワイルドカードは「任意のタイプ」を意味するものではありません。それは「未知のタイプ」を意味します。List<?> i?

たとえば、整数リストを使用してメソッドを実行することにより、i.getから整数値を取得することがわかります。

それは本当ですが、上で述べたように、コンパイラは、コンパイル時に、の上限である、を返すことしか知ることができません。ただし、実行時の保証はないため、コンパイラがそれが安全な呼び出しであることを知る方法はありません。これを書くようなものです:i.get(0)Object?? Objecti.set(0, i.get(0))

List<Foo> fooz = /* init */;
Object foo = fooz.get(0);
fooz.set(0, foo); // won't compile because foo is an object, not a Foo

もっと読む:

于 2012-08-20T19:32:46.077 に答える
2

Get-Putの原則によると:

  1. のようにワイルドカードを拡張した場合は、次のようにList<? extends Something>なります。

    1A。Somethingまたはそのsuperclass参照を使用して構造から取得できます。

    void foo(List<? extends Number> nums) {
       Number number = nums.get(0); 
       Object number = nums.get(0); // superclass reference also works.
    }
    

    1B。構造に何も追加できません(を除くnull)。

    void foo(List<? extends Number> nums) {
       nums.add(1); Compile error
       nums.add(1L); Compile error
       nums.add(null); // only null is allowed.
    }
    
  2. 同様に、のようにスーパーワイルドカードを使用している場合は、次のようList<? super Something>になります。

    2A。Somethingまたはその。である構造に追加できますsubclass。例:

    void foo(List<? super Number> nums) {
        nums.add(1); // Integer is a subclass of Number
        nums.add(1L); // Long is a subclass of Number
        nums.add("str"); // Compile error: String is not subclass of Number         
    }
    

    2A。構造から取得することはできません(オブジェクト参照を介する場合を除く)。例:

    void foo(List<? super Integer> nums) {
        Integer num = nums.get(0); // Compile error
        Number num = nums.get(0); // Compile error
        Object num = nums.get(0); // Only get via Object reference is allowed.        
    }
    

OPの質問に戻ると、List<?> iはの短い表現にすぎませんList<? extends Object> i。また、extendsワイルドカードであるため、set操作は失敗します。

残っている最後のピースは、なぜ操作が失敗するのですか?それとも、そもそもなぜGet-Putの原則なのか?-これは、ここでJonSkeetが回答した型安全性と関係があります。

于 2018-08-11T19:29:11.377 に答える
1

また、この質問は理解しにくいと思います。1つのコマンドを2つのコマンドに分割することは私を助けました。

以下のコードは、元のメソッドが検査およびコンパイルされたときにバックグラウンドで実際に発生することです。コンパイラーは独自のローカル変数を作成します。i.get(0)呼び出しの結果は、ローカル変数スタックのレジスターに配置されます。

これは、この問題を理解するために、便宜上名前を付けたローカル変数を作成するのと同じelementです。

import java.util.List;
public class WildcardError {
 void foo(List<?> i) {
  Object element = i.get(0);  // command 1
  i.set(0, element);          // command 2
 }
}

コマンド1を検査する場合、変数タイプとして使用できないため、タイプをelementに設定することしかできませんObject(->上限の概念。マットの回答を参照) 。?これ?は、ジェネリック型が不明であることを示すためにのみ使用されます。

変数型は実数型またはジェネリック型のみですが、<T>たとえばこのメソッドではジェネリック型を使用しないため、実数型を使用する必要があります。この強制は、Java仕様(jls8、18.2.1)の次の行のために行われます。

‹式→T›の形式の制約式は、次のように簡略化されます。

[...]

–式がクラスインスタンス作成式またはメソッド呼び出し式の場合、制約は、§18.5.2で定義されているように、Tをターゲットにするときに式の呼び出しタイプを決定するために使用されるバインドされたセットB3に縮小されます。(クラスインスタンス作成式の場合、推論に使用される対応する「メソッド」は§15.9.3で定義されています)。

于 2015-07-23T15:59:04.977 に答える
1

解決策は、

import java.util.List;

    public class WildcardError {

    private void fooHelper(List<T> i){
         i.set(0, i.get(0));
    }
    public void foo(List<?> i){
         fooHelper(i);
    }
}

ここでfooHelperはワイルドカードのタイプTをキャプチャしますか?(ワイルドカードキャプチャという名前のように)。

于 2018-07-13T13:26:28.223 に答える
0

?制限についてのあなたの誤解は、あなたの心の中での any typeObjectまたはそのようなものの代用から来ていると思います。しかし、その仮定は正しくありません。?実際には、を意味しunknown typeます。したがって、次の行で

fooz.set(0, foo);

あるタイプの変数を未知のタイプの変数に割り当てようとしています(関数のシグネチャはのようなものvoid set(int, ?)であるため)。これは、タイプが何であれ、決して不可能fooです。あなたの場合、fooのタイプはであり、実際には、またはその他のObject未知のタイプの変数に割り当てることはできません。FooBar

于 2019-08-07T05:32:34.697 に答える
0

?あなたはジェネリックスで誤解していると思います。実行時の「ワイルドカードキャプチャ」ではありません。コンパイル時の「ワイルドカードキャプチャ」です。そのキャプチャはコンパイル後に失われるため、List<?>多くList<Object>の場合、ジェネリック型に従ってインスタンス間で共有される最下位のサブクラスであるインターフェイスまたは共通ベースクラスのリストになります。

JavaはジェネリックシステムをJVMをサポートする非ジェネリックと互換性を持たせる必要があったため、実行時にジェネリックタイプはありません。これは、実行時にすべてのチェック/キャプチャが存在しないことを意味します。コンパイラーは、コンパイル時に非常に狭い範囲の互換性のあるGenerics型の割り当てを許可するか、エラーをスローします。

実行時にジェネリック型を保持したい場合は、いくつかのアプローチがあります。それらのほとんどは、Classオブジェクトをパラメーターとしてコンストラクターに渡すことを含みます。コンストラクターはそれをフィールドに保持します。次に、正確に渡されたタイプのフィールドを参照できます。このタイプをGenericsパラメーターと絡ませることができます。そうすることで、「ジェネリック型パラメーター」が実際のJava型システムでサポートされている最も許容度の高い型に置き換えられたランタイムコードの場所に実際の型を配置できます。これは多くの作業であるため(そしてセキュリティが大幅に向上する可能性は低いため)、多くの状況でこれを行うことはお勧めしません。しかし、時々あなたは本当にタイプを必要とします。

于 2021-12-13T14:14:15.187 に答える