間に違いはありますか
List<Map<String, String>>
そして
List<? extends Map<String, String>>
?
違いがない場合、? extends
を使用する利点は何ですか?
違いは、たとえば、
List<HashMap<String,String>>
は
List<? extends Map<String,String>>
しかしではない
List<Map<String,String>>
そう:
void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}
void main( String[] args ){
List<HashMap<String,String>> myMap;
withWilds( myMap ); // Works
noWilds( myMap ); // Compiler error
}
List
のHashMap
はList
のMap
であると考えられますが、そうではない理由があります。
あなたができると仮定します:
List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();
List<Map<String,String>> maps = hashMaps; // Won't compile,
// but imagine that it could
Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap
maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)
// But maps and hashMaps are the same object, so this should be the same as
hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)
そのため、List
of HashMap
sをList
of Map
sにしないでください。
List<NavigableMap<String,String>>
などのタイプの式を最初に割り当てることはできません。
(List<String>
をList<Object>
に割り当てられない理由を知りたい場合は、SOでzillionの他の質問を参照してください。)
私が他の答えに欠けているのは、これが一般に共変および反変、サブタイプおよびスーパータイプ(つまり、ポリモーフィズム)と特にJavaにどのように関係するかについての参照です。これはOPによってよく理解されるかもしれませんが、念のため、ここで説明します。
クラスAutomobile
がある場合、Car
とTruck
はサブタイプです。任意のCarをAutomobile型の変数に割り当てることができます。これはOOでよく知られており、ポリモーフィズムと呼ばれます。共分散とは、ジェネリックまたはデリゲートのシナリオでこの同じ原則を使用することを指します。 Javaには(まだ)デリゲートがないため、この用語はジェネリックのみに適用されます。
私は共分散を標準的なポリモーフィズムと考える傾向があります。何も考えずに機能すると期待されるものです。
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
ただし、エラーの理由は正しいです:List<Car>
does notはList<Automobile>
を継承するため、互いに割り当てることはできません。ジェネリック型パラメーターのみに継承関係があります。 Javaコンパイラーは、そこでのシナリオを適切に理解するのに十分なほどスマートではないと考えるかもしれません。ただし、コンパイラーにヒントを与えることで、コンパイラーを支援できます。
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
共分散の逆は反分散です。共分散ではパラメータータイプはサブタイプの関係を持つ必要がありますが、反分散ではスーパータイプの関係が必要です。これは、継承の上限と見なすことができます。指定されたタイプを含む、すべてのスーパータイプが許可されます。
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
これは、 Collections.sort で使用できます。
public static <T> void sort(List<T> list, Comparator<? super T> c)
// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());
オブジェクトを比較し、任意のタイプで使用する比較器で呼び出すこともできます。
少しOTおそらくあなたは尋ねなかったが、それはあなたの質問への答えを理解するのに役立つ。一般に、get何かの場合は共分散を使用し、put何かの場合は反分散を使用します。これは、 Stack Overflowの質問への回答Javaジェネリックスで反分散をどのように使用しますか? で最もよく説明されています。
List<? extends Map<String, String>>
の場合はどうなりますかextends
を使用するため、covarianceのルールが適用されます。ここにはマップのリストがあり、リストに保存する各アイテムはMap<string, string>
であるか、そこから派生している必要があります。ステートメントList<Map<String, String>>
はMap
から派生できませんが、aMap
でなければなりません。
したがって、TreeMap
はMap
を継承するため、以下が機能します。
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
しかし、これはしません:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
共分散制約を満たさないため、これも機能しません。
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>()); // This is NOT allowed, List does not implement Map
これはおそらく明らかですが、extends
キーワードの使用はそのパラメーターにのみ適用され、残りには適用されないことに既に気付いているかもしれません。つまり、以下はコンパイルされません:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
文字列としてキーを使用して、マップ内の任意のタイプを許可する場合、各タイプパラメーターでextend
を使用できます。つまり、XMLを処理していて、AttrNode、Elementなどをマップに保存したい場合、次のようなことができます。
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
今日、私はこの機能を使用しているので、ここに私の非常に新鮮な実際の例があります。 (クラスとメソッドの名前を一般的なものに変更したので、実際のポイントから気を散らすことはありません。)
私はこの署名で最初に書いたSet
of A
オブジェクトを受け入れることを意図したメソッドを持っています:
void myMethod(Set<A> set)
しかし、Set
のサブクラスのA
sで実際に呼び出したいのです。しかし、これは許可されていません! (その理由は、myMethod
はset
型のオブジェクトをA
に追加できますが、set
のオブジェクトが呼び出し側にあると宣言されているサブタイプではありませんそのため、可能であれば型システムを壊す可能性があります。)
ここで、ジェネリックが助けになります。このメソッドシグネチャを代わりに使用すると、意図したとおりに機能するためです。
<T extends A> void myMethod(Set<T> set)
または、メソッド本体で実際の型を使用する必要がない場合:
void myMethod(Set<? extends A> set)
このように、set
の型はA
の実際のサブタイプのオブジェクトのコレクションになるため、型システムを危険にさらすことなくサブクラスでこれを使用することが可能になります。
あなたが述べたように、リストを定義する以下の2つのバージョンがあります。
List<? extends Map<String, String>>
List<?>
2は非常にオープンです。任意のオブジェクトタイプを保持できます。これは、特定のタイプのマップが必要な場合には役に立たない場合があります。誰かが誤って別のタイプのマップ、たとえばMap<String, int>
を配置した場合に備えて。コンシューマーメソッドが壊れる可能性があります。
List
が特定のタイプのオブジェクトを保持できるようにするために、Javaジェネリックが? extends
を導入しました。したがって、#1では、List
はMap<String, String>
タイプから派生した任意のオブジェクトを保持できます。他のタイプのデータを追加すると、例外がスローされます。