そもそも,「クラス=型」 にした理由は, プログラムのエラーを 「静的に」(コンパイル時) に見つけるための型 を,クラスと同等に扱うことにより,クラス間の関係の意味も含んだ型として,静的にエラーを発見しようとするためであった.しかし,「多重定義」,「オーバーライド」,「サブシュー ム」の性質などに より,静的にエラーのチェックをすることが不可能であることがわかってきた. その例を紹介しよう.
まず,サブシュームの性質は,上で紹介した代入演算の他に,オブジェクトを メソッドの引数で受渡した場合にも生ずる.
public class C {
public void C1() {
System.out.println("calling C1 of C.");
}
public void C2() {
System.out.println("calling C2 of C.");
}
public static void main(String args[]) {
E e = new E();
e.E1(new D());
}
}
class D extends C {
public void C1() {
System.out.println("calling C1 of D.");
}
public void D1() {
System.out.println("calling D1 of D.");
}
}
class E {
public void E1(C c) {
c.C1();
}
}
このプログラムを実行すると以下のようになる.
calling C1 of D.
e.E1(new D());しかし,受け口であるクラス E の定義では,以下のようになっている.
public void E1(C c) {
c.C1();
}
クラス C のインスタンスを受付けるようになっている.これは,クラス D がクラス C のサブクラスであるため サブシュームによりクラス D のインスタンスを受け付けることができ るためである.
つまり,オブジェクトをメソッドの引数として受渡す場合は,対応するク ラスのサブクラスのどれかが受渡される可能性がある.例えば,上の例では,実際にはクラス D のインスタンスを受渡しているので,
public void E1(C c) {
c.D1();
}
public void E1(C c) {
D d=(D)c;
d.D1();
}
これで,めでたく実際のメソッドを呼出すことが可能となったわけであるが, キャストは,非常に危うい性質を持っている.例えば,別のオブジェクトが,
e.E1(new C());
最後に,このような場合の最もエレガントなプログラミングを紹介しよう.
public void E1(C c) {
if(c instanceof D) {D d=(D)c; d.D1();}
else if(c instanceof C) c.C1();
}
一般に,「クラス=型」 であるオブジェクト指向言語でも, メソッドの引数として,オブジェクトを受渡す場合には,ちょうどそのクラス (型) のオブジェクトだけでなく,サブクラスのどれかが受渡される可能性がある.そのためには,本来型という「静的にチェックできるはず」の機能も, その範疇ではチェックしきれないことが知られている. そのために,Java では「動的に」サブクラス関係をチェックする instanceof という演算子が提供されている.また,他の同様のオブジェクト 指向言語でも同様の演算子が提供されている7プログラマは,どこで動的にチェックする必要があるかを常に,意識しなけれ ばならない.