Search on the blog

2014年7月13日日曜日

Covariance、Contravariance、Invariance

 JavaとかC#とか使ってたらたまに見かける言葉。
理解が曖昧だったので、調べてみました。

定義
以下では、クラスAがクラスBの子クラスであるという関係を A ≦ Bのように表します。

今、A ≦ Bであるとする。
このときクラスの写像fは、
  1. f(A) ≦ f(B)を満たすならば、covariantである。
  2. f(B) ≧ f(A)を満たすならば、contravariantである。
  3. どちらも満たさないならば、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 件のコメント:

コメントを投稿