web-dev-qa-db-ja.com

Javaネストされたジェネリック型

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

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) {}
26
Louis Bliss

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

実際には、一方が他方のサブタイプである場合がそうですが、最初に、それらが個々に何を意味するかについてさらに学習しましょう。

意味の違いを理解する

一般的に言って、ワイルドカード_?_は、「欠落している情報」を表します。これは"ここでは型引数が1回ありましたが、それが何であるかはもうわかりません"です。また、それが何であるかわからないため、その特定の型引数を参照するものをどのように使用できるかに制限が課されます。

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

  • _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) );
    _

    ListtheAnyListに入れることができますが、そうすることでtheir elementsの知識が失われます。

  • _? 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<?>>_を作成できます。そこにどのタイプのListが含まれているかはわかりません。また、要素のタイプthoseListsもわかりません。つまり:

    _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" );
    _

    情報が失われました両方theReallyNotSureListについて同様に中のListsの要素タイプ。

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

それを分解するには:

_//   ┌ 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_が必要な理由

"concrete" ジェネリック型にはinvarianceがあること、つまり _List<Dog>_は_List<Animal>_ のサブタイプではないことがわかります= _class Dog extends Animal_の場合でも。代わりに、ワイルドカードはcovariance、つまり_List<Dog>_ isのサブタイプであり、_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>>_はnot _List<List<?>>_のサブタイプです。前に示したように、これはListに間違った要素を追加することによって型の安全性を損なうことを防ぎます。
  • _List<List<String>>_ is _List<? extends List<?>>_のサブタイプ。制限付きワイルドカードは共分散を許可するためです。つまり、_? extends_は、_List<String>_が_List<?>_のサブタイプであることを考慮することを許可します。
  • _List<? extends List<?>>_は、実際には共有スーパータイプです。

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

レビュー中

  1. _Map<Integer, List<String>>_は、値としてonly _List<String>_を受け入れます。
  2. _Map<?, List<?>>_は、値としてanyListを受け入れます。
  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の-​​allMapsが同じ要素タイプを持っている必要があります。そのタイプが実際には何であるかはわかりませんisですが、抽象的な方法で使用できます。これにより、「ブラインド」操作を実行できます。

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

_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;
}
_

key typeが何であるかわからないため、putmを呼び出すことはできません。ただし、すべてのListが同じ要素タイプであることを理解しているため、そのを操作できます。

キックだけのために

質問で説明されていない別のオプションは、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とその中のすべてを操作する方法について許容します。


こちらもご覧ください

49
Radiodef

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

class A{}
class B extends A{}

その後

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

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

1
Bartek Maraszek