違いはありますか
List<Map<String, String>>
そして
List<? extends Map<String, String>>
?
違いがない場合、使用するメリットは何? extends
ですか?
違いはありますか
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
sがあるべきList
のMap
Sが、それはない正当な理由があります:
あなたができると仮定します:
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
のHashMap
sがあってはならないList
のMap
秒。
HashMap
あるMap
多型に起因します。
List
のHashMap
sがないList
のMap
秒。
List<Map<String,String>> maps = hashMaps;
した場合でも、合法であるHashMap<String,String> aMap = new HashMap<String, String>();
にもかかわらず、それmaps.add(aMap);
が違法であることに気付くことですhashMaps.add(aMap);
。目的は間違った型の追加を防ぐことですが、正しい型の追加は許可されません(コンパイラーはコンパイル時に「正しい」型を判別できません)
HashMap
Map
List<NavigableMap<String,String>>
最初のようなタイプの式を割り当てることはできません。
(SOに関する膨大な数の他の質問List<String>
をList<Object>
表示するように割り当てることができない理由を知りたい場合。)
? extends
。また、スーパー/サブタイプまたは共分散(存在する場合)との相関関係についても説明していません。
他の回答で私が見落としているのは、これが共変量と反変量、およびサブタイプとスーパータイプ(つまり、ポリモーフィズム)全般、特にJavaとどのように関連しているかについての参照です。これはOPによってよく理解されているかもしれませんが、念のため、ここに示します。
あなたはクラスを持っている場合はAutomobile
、その後、Car
およびTruck
そのサブタイプです。自動車はタイプAutomobileの変数に割り当てることができます。これはオブジェクト指向ではよく知られており、ポリモーフィズムと呼ばれます。共分散とは、ジェネリックまたはデリゲートを使用するシナリオでこれと同じ原則を使用することを指します。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>
は継承されないため、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はおそらく質問しなかったかもしれませんが、質問への回答を理解するのに役立ちます。一般に、何かを取得する場合は共分散を使用し、何かを配置する場合は反変を使用します。これは、スタックオーバーフローの質問への回答で最もよく説明されます。Javaジェネリックスではどのように反変が使用されますか?。
List<? extends Map<String, String>>
を使用するextends
ため、共分散のルールが適用されます。ここにはマップのリストがあり、リストに保存する各アイテムは、そのリストMap<string, string>
から派生するか、派生する必要があります。文はList<Map<String, String>>
から派生することはできませんMap
が、なければなりません。 Map
したがって、以下から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>());
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());
結果はfound: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without bounds
です。List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());
完璧に動作します。最後の例は明らかに正しいです。
今日、私はこの機能を使用したので、これが私の非常に新鮮な実際の例です。(私はクラスとメソッドの名前を一般的なものに変更したので、それらは実際のポイントから邪魔されません。)
を受け入れることを意図したメソッドがSet
ありますA
最初にこのシグネチャで書いたオブジェクトのます:
void myMethod(Set<A> set)
しかし、それは実際Set
にのサブクラスのsでそれを呼び出したいですA
。しかし、これは許可されていません!(その理由は、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>
。他のタイプのデータを追加すると、例外がスローされます。