今日、Java における共変性、反変性 (および不変性) に関する記事をいくつか読みました。英語とドイツ語の Wikipedia の記事、および IBM のその他のブログ投稿と記事も読みました。
しかし、これらが正確に何に関するものなのか、まだ少し混乱しています。型とサブタイプの関係に関するものだと言う人もいれば、型変換に関するものだと言う人もいれば、メソッドがオーバーライドされるかオーバーロードされるかを決定するために使用されると言う人もいます。
そこで私は、共変性と反変性(および不変性)が何であるかを初心者に示す、平易な英語による簡単な説明を探しています。簡単な例があればプラスポイントです。
ベストアンサー1
これは型とサブタイプの関係に関するものだと言う人もいれば、型変換に関するものだと言う人もいれば、メソッドが上書きされるかオーバーロードされるかを決定するために使用されると言う人もいます。
上記のすべて。
本質的に、これらの用語は、サブタイプ関係が型変換によってどのように影響を受けるかを説明します。つまり、A
とがB
型で、f
が型変換であり、≤ サブタイプ関係(つまり、が のサブタイプであるA ≤ B
ことを意味する)の場合、A
B
f
A ≤ B
が共変である場合、f(A) ≤ f(B)
f
A ≤ B
が反変である場合、f(B) ≤ f(A)
f
上記のどちらも当てはまらない場合は不変である
例を考えてみましょう。 がf(A) = List<A>
次のようList
に宣言されているとします。
class List<T> { ... }
f
は共変、反変、不変のどれでしょうか。共変とは、aList<String>
が のサブタイプであること、List<Object>
反変とは、a がList<Object>
のサブタイプであることList<String>
、不変とは、どちらも他方のサブタイプではないこと、つまり、List<String>
と はList<Object>
変換不可能な型であることを意味します。Java では、後者が真であり、次のように言います (やや非公式に)。ジェネリック不変です。
別の例です。は共変、反変、不変のどれf(A) = A[]
でしょうかf
? つまり、String[] は Object[] のサブタイプでしょうか、Object[] は String[] のサブタイプでしょうか、それともどちらも他方のサブタイプではないでしょうか? (答え: Java では、配列は共変です)
これはまだかなり抽象的です。より具体的にするために、Javaのどの操作がサブタイプの関係で定義されているかを見てみましょう。最も簡単な例は代入です。
x = y;
は の場合にのみコンパイルされますtypeof(y) ≤ typeof(x)
。つまり、文
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
Javaではコンパイルできませんが、
Object[] objects = new String[1];
意思。
サブタイプの関係が重要になる別の例は、メソッド呼び出し式です。
result = method(a);
非公式に言えば、この文は、 の値をa
メソッドの最初のパラメータに割り当て、メソッドの本体を実行し、メソッドの戻り値を に割り当てることによって評価されます。最後の例の単純な割り当てと同様に、「右側」は「左側」のサブタイプである必要があります。つまり、この文はおよびresult
の場合にのみ有効です。つまり、メソッドが次のように宣言されている場合:typeof(a) ≤ typeof(parameter(method))
returntype(method) ≤ typeof(result)
Number[] method(ArrayList<Number> list) { ... }
次の式はいずれもコンパイルされません。
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
しかし
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
意思。
サブタイプが重要になるもう 1 つの例はオーバーライドです。次の例を検討してください。
Super sup = new Sub();
Number n = sup.method(1);
どこ
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
非公式には、ランタイムはこれを次のように書き換えます。
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
マークされた行をコンパイルするには、オーバーライドするメソッドのメソッド パラメータがオーバーライドされるメソッドのメソッド パラメータのスーパータイプであり、戻り値の型がオーバーライドされるメソッドの戻り値の型サブタイプである必要があります。正式には、f(A) = parametertype(method asdeclaredin(A))
少なくとも反変である必要があり、f(A) = returntype(method asdeclaredin(A))
少なくとも共変である必要があります。
上記の「少なくとも」に注意してください。これらは、静的に型安全なオブジェクト指向プログラミング言語であれば必ず強制する最小要件ですが、プログラミング言語によっては、より厳格にすることもできます。Java 1.4 の場合、メソッドをオーバーライドする場合、つまりparametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
オーバーライドする場合、パラメータの型とメソッドの戻り値の型は同一である必要があります (型の消去を除く)。Java 1.5 以降では、オーバーライド時に共変の戻り値の型が許可されています。つまり、次のコードは Java 1.5 ではコンパイルされますが、Java 1.4 ではコンパイルされません。
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
すべてをカバーできたこと、というか、表面を少し触れただけだったことを願っています。それでも、抽象的だが重要な型の変化の概念を理解するのに役立つことを願っています。