28

次のメソッドMap<?, ? extends List<?>>では、単純な型ではなくジェネリック型を使用する必要があるのはなぜですか?Map<?, List<?>>test()

public static void main(String[] args) {
    Map<Integer, List<String>> mappy =
        new HashMap<Integer, List<String>>();

    test(mappy);
}

public static void test(Map<?, ? extends List<?>> m) {}

// Doesn't compile
// public static void test(Map<?, List<?>> m) {}

以下が機能することに注意してください。また、3 つのメソッドの消去タイプはいずれにしても同じであることに注意してください。

public static <E> void test(Map<?, List<E>> m) {}
4

2 に答える 2

67

基本的に、List<List<?>>異なるList<? extends List<?>>型の引数があります。

実際には一方が他方のサブタイプである場合がありますが、最初にそれぞれが何を意味するのかを詳しく学びましょう。

セマンティックの違いを理解する

一般的に言えば、ワイルドカード?は「不足している情報」を表します。「ここにはかつて型引数がありましたが、それが何であるかはわかりません」という意味です。そして、それが何であるかわからないため、その特定の型引数を参照するものをどのように使用できるかについて制限が課せられます。

Listとりあえず、 の代わりに を使用して例を単純化しましょうMap

  • Aは、任意の型引数を持つ任意の種類の List をList<List<?>>保持します。つまり:

    List<List<?>> theAnyList = new ArrayList<List<?>>();
    
    // we can do this
    theAnyList.add( new ArrayList<String>() );
    theAnyList.add( new LinkedList<Integer>() );
    
    List<?> typeInfoLost = theAnyList.get(0);
    // but we are prevented from doing this
    typeInfoLost.add( new Integer(1) );
    

    任意Listのを入れることができますtheAnyListが、そうすることで、それらの要素の知識を失ってしまいます。

  • を使用する? extendsと、 List の特定のサブタイプがList保持されますが、それが何であるかはわかりません。つまり:

    List<? extends List<Float>> theNotSureList =
        new ArrayList<ArrayList<Float>>();
    
    // we can still use its elements
    // because we know they store Float
    List<Float> aFloatList = theNotSureList.get(0);
    aFloatList.add( new Float(1.0f) );
    
    // but we are prevented from doing this
    theNotSureList.add( new LinkedList<Float>() );
    

    theNotSureList要素の実際の型がわからないため、に何かを追加することはもはや安全ではありません。(元々は?List<LinkedList<Float>>それともList<Vector<Float>>? わかりません。)

  • これらをまとめてList<? extends List<?>>. その中にどのタイプが含まれているかはわかりません。また、それらの s のList要素タイプもわかりません。つまり: List

    List<? extends List<?>> theReallyNotSureList;
    
    // these are fine
    theReallyNotSureList = theAnyList;
    theReallyNotSureList = theNotSureList;
    
    // but we are prevented from doing this
    theReallyNotSureList.add( new Vector<Float>() );
    // as well as this
    theReallyNotSureList.get(0).add( "a String" );
    

    に関する情報と、その中の の要素タイプの両方が失われました。theReallyNotSureListList

    (ただし、リストを保持する任意の種類のリストをそれに割り当てることができることに注意してください...)

それを分解するには:

//   ┌ applies to the "outer" List
//   ▼
List<? extends List<?>>
//                  ▲
//                  └ applies to the "inner" List

Map同じように機能しますが、型パラメーターが増えるだけです。

//  ┌ Map K argument
//  │  ┌ Map V argument
//  ▼  ▼
Map<?, ? extends List<?>>
//                    ▲
//                    └ List E argument

なぜ? extends必要なのか

「具体的な」ジェネリック型には不変性があること、つまりifList<Dog>のサブタイプではないList<Animal>ことを知っているかもしれませんclass Dog extends Animal。代わりに、ワイルドカードは共分散をどのように持つか、つまりList<Dog> サブタイプですList<? extends Animal>

// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}

// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();

// all parameterized Lists are subtypes of List<?>
List<?> b = a;

したがって、これらのアイデアをネストされた に適用しますList

  • List<String>は のサブタイプですList<?>が、 のサブタイプでList<List<String>>はありませんList<List<?>>。前に示したように、これにより、間違った要素を に追加して型の安全性を損なうことを防ぎListます。
  • List<List<String>> List<? extends List<?>>境界のあるワイルドカードによって共分散が可能になるため、のサブタイプです。つまり、が のサブタイプである? extendsという事実を考慮することができます。List<String>List<?>
  • List<? extends List<?>>実際には共有スーパータイプです。

         List<? extends List<?>>
              ╱          ╲
    List<List<?>>    List<List<String>>
    

審査中

  1. Map<Integer, List<String>>値としてのみ 受け入れList<String>ます。
  2. Map<?, List<?>>any を値として受け入れListます。
  3. Map<Integer, List<String>>およびMap<?, List<?>>は、別個のセマンティクスを持つ別個の型です。
  4. 安全でない方法で変更を行うことを防ぐために、一方を他方に変換することはできません。
  5. Map<?, ? extends List<?>>安全な制限を課す共有スーパータイプです:

            Map<?, ? extends List<?>>
                 ╱          ╲
    Map<?, List<?>>     Map<Integer, List<String>>
    

ジェネリック メソッドのしくみ

Listメソッドで型パラメーターを使用することにより、具体的な型があるとアサートできます。

static <E> void test(Map<?, List<E>> m) {}

この特定の宣言では、内のすべて Listの がMap同じ要素型を持つ必要があります。その型が実際に何であるかはわかりませんが抽象的に使用できます。これにより、「ブラインド」操作を実行できます。

たとえば、この種の宣言は、ある種の累積に役立つ場合があります。

static <E> List<E> test(Map<?, List<E>> m) {
    List<E> result = new ArrayList<E>();

    for(List<E> value : m.values()) {
        result.addAll(value);
    }

    return result;
}

キーの種類がわからなくなっputたため、呼び出すことができません。ただし、それらはすべて同じ要素タイプであることがわかっているため、そのを操作できます。mList

キックのためだけに

質問で説明されていない別のオプションは、境界付きワイルドカードとジェネリック型の両方を使用することListです。

static <E> void test(Map<?, ? extends List<E>> m) {}

のようなもので呼び出すことができますMap<Integer, ArrayList<String>>。の型のみを気にする場合、これは最も寛大な宣言ですE

境界を使用して型パラメーターをネストすることもできます。

static <K, E, L extends List<E>> void(Map<K, L> m) {
    for(K key : m.keySet()) {
        L list = m.get(key);
        for(E element : list) {
            // ...
        }
    }
}

これは、渡すことができるものについて寛容であると同時に、操作方法mとその中​​のすべてについても寛容です。


こちらもご覧ください

于 2014-04-02T09:10:22.547 に答える
1

これは、ジェネリックのサブクラス化規則が予想とは少し異なるためです。特にあなたが持っている場合:

class A{}
class B extends A{}

それから

List<B>のサブクラスではありませんList<A>

ここで詳細に説明されており、ワイルドカード (「?」文字) の使用法はここで説明されています

于 2014-04-02T09:18:25.090 に答える