理解が曖昧だったので、調べてみました。
定義
以下では、クラスAがクラスBの子クラスであるという関係を A ≦ Bのように表します。今、A ≦ Bであるとする。
このときクラスの写像fは、
- f(A) ≦ f(B)を満たすならば、covariantである。
- f(B) ≧ f(A)を満たすならば、contravariantである。
- どちらも満たさないならば、invariantである。
これだけだと抽象的なので、Javaを例に考えてみます。
配列はcovariant
Javaの場合、配列はcovariantです。配列を型Tから型T[]への写像fであると考えると、定義の条件1が成り立ちます。
具体例を挙げると、Integer[] は Number[] の小クラスです。
よって以下のようなプログラムを書くことが出来ます。
package com.kenjih.sample; public class Sample1 { public static void main(String[] args) { // 配列はcovariant Number[] numbers = new Integer[10]; Object[] objects = new Integer[10]; } }
genericsはinvariant
genericタイプのコレクションはinvariantです。
以下のようなプログラムはコンパイルエラーになります。配列はcovariantなのに、genericsはinvariantというのはなかなか厄介です。
package com.kenjih.sample; import java.util.ArrayList; public class Sample2 { public static void main(String[] args) { // genericsはinvariant // Type mismatch: cannot convert from ArrayList<Integer> to ArrayList<Number> ArrayList<Number> numbers = new ArrayList<Integer>(); // もちろんcontravariantでもない。 // Type mismatch: cannot convert from ArrayList<Number> to ArrayList<Integer> ArrayList<Integer> integers = new ArrayList<Number>(); } }
メソッドの引数はcontravariant、メソッドの戻り値はcovariant
A ≦ Bであるとします。クラスAでクラスBのメソッドをoverrideします。
このとき、型TからTのメソッドの引数への写像、型TからTのメソッドの戻り値への写像がどのような性質をみたすべきか考えます。
以下のソースコードから、
- 子クラスのメソッドの引数の型 ≧ 親クラスのメソッドの引数の型
- 子クラスのメソッドの戻り値の型 ≦ 親クラスのメソッドの戻り値の型
実際のJavaではオーバーライドに関する型指定はより厳密で、Java 1.4の場合は引数、戻り値の型ともに完全一致でなければならず、Java 1.5以降ではcovariantな戻り値の型でもオーバーライド出来るらしいです。
package com.kenjih.sample; public class Sample3 { public static Number doit(Number x) { return x; } public static void main(String[] args) { Integer x = Integer.valueOf(0); Number y = x; Object z = x; // parameter type test Number ret3 = doit(x); // f: Integer -> Number OK Number ret4 = doit(z); // f: Object -> Number Compile Error // return type test Integer ret1 = doit(y); // f: Number -> Integer Compile Error Object ret2 = doit(y); // f: Number -> Object OK } }
0 件のコメント:
コメントを投稿