Revised: Nov./19th/2003; Since: May/31st/2002
前節までで、継承、抽象クラス、インタフェースについて紹介しました。スーパークラス型への代入/インタフェース型への代入はオブジェクト指向の重要な特徴です。メソッド引数に異なる型のオブジェクトを代入することで、メソッドの働きが変わることを多態性と呼びます。
スーパークラス型の変数には、サブクラス型のオブジェクト参照を保持することが可能です。また、もともとサブクラス型で生成されたオブジェクトの参照は、スーパー・クラス型からサブクラス型に明示的なキャストによって代入互換(型互換)です。但し、自分が生成されたクラスのサブクラスへの型変換は不可能です。サブクラス型オブジェクトへの参照が代入されたスーパークラス型変数に対して、サブクラスでオーバーライドされたメソッドを呼び出すと、当該オブジェクトを生成したクラスにて定義したオーバーライド・メソッドを起動します。
スーパー・インタフェースとサブインタフェース、インタフェースと実装クラスの場合も同様です。とくに、インタフェース型変数へ実装クラス型オブジェクトの参照を代入する設計は、操作が統一的で柔軟性が高くなります。同じクラス型の変数に同じシグネチャでメソッド呼び出しをしても、オブジェクト自身のオリジナルの型に応じて適切なメソッドが実行されるため、実装の切り替えに対して、当該オブジェクトのメソッド呼び出しをするクライアント・コードを変更する必要がなくなるからです。
extends以前紹介した通り、クラスは他のクラスを、ただ一つだけ継承することができます。次の例は、 Score クラスを継承している ScoreEnglish クラスと ScoreMath クラスです。Score クラスにはメソッド宣言だけで、処理は記述していません。
class Score {
void setScores(int[] vals) {}
double calc() {
return 0;
}
}
class ScoreEnglish extends Score {
int[] scores;
void setScores(int[] vals) {
scores = vals;
}
double calc() {
double total = 0;
for (int i = 0; i < scores.length; i++) {
total += scores[i];
}
return total;
}
}
class ScoreMath extends Score {
int[] scores;
void setScores(int[] vals) {
scores = vals;
}
double calc() {
double total = 0, mean = 0;
for (int i = 0; i < scores.length; i++) {
total += scores[i];
}
mean = total/scores.length;
return mean;
}
}
このクラスを利用するコントロールクラスを作って、動作を確認しましょう。次の例では、 main() メソッドで MathScore クラスをインスタンス化しています。そのオブジェクトを PolymorphismView 型オブジェクトのメソッド getData() に、メソッド引数で渡しています。
PolymorphismView クラスの方では、Score 型オブジェクトを引数にとるように定義しています。スーパークラス型変数には、サブクラス型変数を代入できるので、 ScoreMath, ScoreEnglish 共に代入可能です。
PolymorphismControl.java:
class PolymorphismControl {
public static void main(String[] args) {
ScoreMath math = new ScoreMath();
int[] mathScores = {5,3,4,6,8,6,4};
ScoreEnglish english = new ScoreEnglish();
int[] englishScores = {7,8,6,10,7,6,4};
PolymorphismView view = new PolymorphismView();
view.getData(math, mathScores);
view.getData(english, englishScores);
}
}
class PolymorphismView {
void getData(Score obj, int[] scores) {
double value = 0;
obj.setScores(scores);
value = obj.calc();
System.out.println(value);
}
}
実行結果:
C:\java>javac PolymorphismControl.java C:\java>java PolymorphismControl 5.142857142857143 48.0 C:\java>
PolymorphismView で呼ばれた calc() メソッドは、 obj にどのオブジェクトが代入されているかによって、実際の処理が変わります。 obj に ScoreMath 型オブジェクトが代入されていれば平均を計算し、 ScoreEnglish 型オブジェクトが代入されていれば総和が計算されます。
このような性質を多態性と呼びます。多様性、ポリモーフィズムなどの言葉も使われます。
上に挙げた例は、 Score クラスはメソッドの宣言をしているだけで、処理の記述をしていません。このような場合は、以前紹介した抽象クラスにしてしまえます。
abstract class Score { abstract void setScores(int[] vals); abstract double calc(); }
この Score クラスのように、必ず継承されて使われることが前提できる場合は、抽象クラスにしておいた方が安全です。抽象メソッドが一つでも残っていれば、インスタンスかできないからです。
インスタンス化しようとすると、次のエラーメッセージが出力されます:
Abstract.java:
class Abstract {
public static void main(String[] args) {
Score obj = new Score();
}
}
C:\java>javac Abstract.java
Abstract.java:3: Score は abstract です。インスタンスを生成することはできません。
Score obj = new Score();
^
エラー 1 個
C:\java>
前項の Score クラスをこれで置き換えて、 PolymorphismControl.java を実行してみれば、完全に同じ実行結果が得られます。
クラスの継承 (extends) は一つのクラスだけでしたが、インタフェースの実装 (implements) は複数のインタフェースが可能でした。
前項の Score クラスは、メンバーのメソッドが全て抽象メソッドなので、そのままインタフェースにすることができます。
interface Score {
void setScores(int[] vals);
double calc();
}
インタフェースの復習です:
abstract public がつくfinal public static がつく以上を考えて、実装クラスは次のように編集します:
class ScoreEnglish implements Score { int[] scores; public void setScores(int[] vals) { scores = vals; } public double calc() { double total = 0; for (int i = 0; i < scores.length; i++) { total += scores[i]; } return total; } } class ScoreMath implements Score { int[] scores; public void setScores(int[] vals) { scores = vals; } public double calc() { double total = 0, mean = 0; for (int i = 0; i < scores.length; i++) { total += scores[i]; } mean = total/scores.length; return mean; } }
以上、書き換えて実行すれば、前項と全く同じ結果が得られます:
C:\java>javac PolymorphismControl.java C:\java>java PolymorphismControl 5.142857142857143 48.0 C:\java>
本項で見てきたとおり、多態性を考えると、サブクラス/実装クラスでは、スーパークラス/インタフェースで宣言されたメソッド以外は記述しない方が望ましいことになります。サブクラスがスーパークラス型変数に代入されたときには、サブクラス独自に宣言して実装したメソッドは使えないからです。
一方、スーパークラス/インタフェースで宣言されていないメソッドや変数を追加することもオブジェクト指向では極めて自然なことです。
継承/実装には相反する二つの側面があります。
何れを選ぶかは、適用する状況に依存するもので、デザインパターンの勉強、コーディング経験の積み上げによって習得されます。