継承の仕組みを利用しますと、一般的なオブジェクトの変数と関数・手続きが、特殊なオブジェクトへ引き継がれます。 しかし、一部は継承してほしくないときもあります。 そのような場合は、オーバーライドと呼ばれることを行います。
ここでは、例として、商店の商品に関する次のような計算を考えます。
番号 | 商品 | 本体価格 | 非課税 | 半額処分 | 販売価格 |
---|---|---|---|---|---|
0 | ボールペン | 100 | × | × | 105 |
1 | ハガキ | 50 | ○ | × | 50 |
2 | ノート | 140 | × | × | 147 |
3 | パン | 120 | × | ○ | 63 |
4 | ジュース | 160 | × | × | 168 |
合計 | 533 |
商品の性質は、本体価格が決まっていることです。 商品の機能は、本体価格が決められることと、それが取り出せることです。 消費税額と販売価格が計算できることも、商品の機能とします。
商品はクラス Goods で表すことにします。 本体価格は、インスタンス変数 price で表します。 本体価格を決めることは、インスタンス・メソッドではなく、コンストラクタで書くことにします。 本体価格を取り出すことは、インスタンス・メソッド getPrice で表します。 インスタンス・メソッド getTax と getSalesPrice で、それぞれ消費税額と販売価格を計算することにします。 すると、クラスの定義プログラムは次のようになります。
/* 1*/ class Goods { /* 2*/ int price; /* 3*/ Goods (int price) { /* 4*/ this.price = price; /* 5*/ } /* 6*/ int getPrice () { /* 7*/ return this.price; /* 8*/ } /* 9*/ int getTax () { /* 10*/ return this.getPrice() * 5 / 100; /* 11*/ } /* 12*/ int getSalesPrice () { /* 13*/ return this.getPrice() + this.getTax(); /* 14*/ } /* 15*/ }
次に、葉書のような非課税商品のクラスを定義します。 クラスの名前は TaxFreeGoods とします。 取りあえず、継承は行わないことにします。
/* 1*/ class TaxFreeGoods { /* 2*/ int price; /* 3*/ TaxFreeGoods (int price) { /* 4*/ this.price = price; /* 5*/ } /* 6*/ int getPrice () { /* 7*/ return this.price; /* 8*/ } /* 9*/ int getTax () { /* 10*/ return 0; /* 11*/ } /* 12*/ int getSalesPrice () { /* 13*/ return this.getPrice() + this.getTax(); /* 14*/ } /* 15*/ }
最後に、半額処分商品を考えます。 これは、通常の販売価格の半額で売る商品です。 このクラス名は HalfPriceGoods とします。 取りあえず、継承を利用しないでクラスを定義します。
/* 1*/ class HalfPriceGoods { /* 2*/ int price; /* 3*/ HalfPriceGoods (int price) { /* 4*/ this.price = price; /* 5*/ } /* 6*/ int getPrice () { /* 7*/ return this.price; /* 8*/ } /* 9*/ int getTax () { /* 10*/ return this.getPrice() * 5 / 100; /* 11*/ } /* 12*/ int getSalesPrice () { /* 13*/ return (this.getPrice() + this.getTax()) / 2; /* 14*/ } /* 15*/ }
クラス Goods , TaxFreeGoods , および HalfPriceGoods を利用しますと、合計金額が計算できます。
/* 1*/ class GoodsMain { /* 2*/ public static void main (String[] args) { /* 3*/ int total = 0; /* 4*/ Goods goods0 = new Goods(100); /* 5*/ TaxFreeGoods goods1 = new TaxFreeGoods(50); /* 6*/ Goods goods2 = new Goods(140); /* 7*/ HalfPriceGoods goods3 = new HalfPriceGoods(120); /* 8*/ Goods goods4 = new Goods(160); /* 9*/ total = total + goods0.getSalesPrice(); /* 10*/ total = total + goods1.getSalesPrice(); /* 11*/ total = total + goods2.getSalesPrice(); /* 12*/ total = total + goods3.getSalesPrice(); /* 13*/ total = total + goods4.getSalesPrice(); /* 14*/ System.out.println(total); /* 15*/ } /* 16*/ }
asiaa1:~/comp3b b08a001$ java GoodsMain 533 asiaa1:~/comp3b b08a001$
非課税商品は、商品の中で特殊なものですので、 TaxFreeGoods のクラス定義は Goods のクラス定義に似ています。 したがって、継承を利用したいところですが、インスタンス・メソッド getTax の部分が違いますので、そのままでは引き継げません。 半額処分商品も特殊な商品ですので、クラス HalfPriceGoods の定義もクラス Goods の定義に似ています。 しかし、インスタンス・メソッド getSalesPrice が違いますので、そのまま継承するわけにはいきません。
このような場合、インスタンス・メソッドをオーバーライドしますと、うまく継承できます。 オーバーライド ( override ) とは、スーパークラスの定義をサブクラスで再定義することです。
インスタンス・メソッドがオーバーライドされますと、スーパークラスのインスタンスについては、スーパークラスのメソッド定義が有効になります。 サブクラスのインスタンスについては、サブクラスのメソッド定義が有効になります。 したがって、スーパークラスからそのまま引き継げないインスタンス・メソッドは、サブクラスでオーバーライドすればよいのです。
なお、インスタンス・メソッドをオーバーライドする場合、スーパークラスのインスタンス・メソッドとサブクラスのインスタンス・メソッドは、返り値のデータ型が一致しなければなりません。 また、コンストラクタについては特別で、定義の本体に
super(argument...);
と書きます。
/* 1*/ class TaxFreeGoods extends Goods { /* 2*/ TaxFreeGoods (int price) { /* 3*/ super(price); /* 4*/ } /* 5*/ int getTax () { /* 6*/ return 0; /* 7*/ } /* 8*/ }
/* 1*/ class HalfPriceGoods extends Goods { /* 2*/ HalfPriceGoods (int price) { /* 3*/ super(price); /* 4*/ } /* 5*/ int getSalesPrice () { /* 6*/ return (this.getPrice() + this.getTax()) / 2; /* 7*/ } /* 8*/ }
インスタンス・メソッドをオーバーライドしますと、スーパークラスのインスタンス・メソッドは隠されてしまいます。 しかし、スーパークラスのメソッド定義とサブクラスのメソッド定義は、大抵の場合は似ています。 もし、サブクラスのメソッド定義でスーパークラスのインスタンス・メソッドが呼び出せるのなら、メソッド定義においても違いのみを書けばよくなります。
キーワード
super
を用いますと、サブクラスのメソッド定義でスーパークラスのインスタンス・メソッドが呼び出せます。
具体的には、
super.methodname(argument...)
と書きます。
クラス
HalfPriceGoods
の場合ですと、インスタンス・メソッド
getSalesPrice
の定義がスーパークラス
Goods
のものと似ています。
そこで、キーワード
super
を使ってスーパークラスのインスタンス・メソッドを呼び出し、その値を2で割るだけにします。
/* 1*/ class HalfPriceGoods extends Goods { /* 2*/ HalfPriceGoods (int price) { /* 3*/ super(price); /* 4*/ } /* 5*/ int getSalesPrice () { /* 6*/ return super.getSalesPrice() / 2; /* 7*/ } /* 8*/ }
このプログラムの難点は、商品が100個に増えると合計の計算が100行になることです。
できれば、配列にインスタンスを格納して、
for
文で合計を計算したいところです。
今までの説明では、クラス C で宣言した変数 v には、クラス C のインスタンスしか格納できませんでした。 実は、変数 v には、クラス C のサブクラスのインスタンスも格納できます。
配列要素についても同様に、クラス C で宣言した配列 a において、配列要素 a [ k ]には、クラス C のサブクラスのインスタンスも格納できます。
変数には宣言したクラスのサブクラスのインスタンスも格納できるということは、変数の宣言を見ただけでは、変数の値のクラスは分からないということです。
これを判定するのが、演算子
instanceof
です、条件
expression isntanceof classname
によって、式 expression の値がクラス classname (または、そのサブクラス)のインスタンスかどうかが分かります。
式の値がどのクラスのインスタンスかが分かりましたら、次にその値をそのクラスで宣言した変数に格納しなおすのが普通です。
しかし、その変数にとっては、
instanceof
でクラスを確認したことなんて知らない話ですので、そのままでは代入できません。
このとき使われるのが、
キャスト演算子
(
cast operator
)
です。
variable = (classname) expression;
のように、括弧に囲まれたクラス名 classname を式 expression の先頭に書きますと、 expression の値は classname のインスタンスであるという指定がなされ、変数への代入が行われるのです。
instanceof
演算子とキャスト演算子は、しばしばセットで使われます。
/* 1*/ class GoodsMain { /* 2*/ public static void main (String[] args) { /* 3*/ int i, total = 0; /* 4*/ Goods g; /* 5*/ TaxFreeGoods tfg; /* 6*/ HalfPriceGoods hpg; /* 7*/ Goods[] goods = { /* 8*/ new Goods(100), /* 9*/ new TaxFreeGoods(50), /* 10*/ new Goods(140), /* 11*/ new HalfPriceGoods(120), /* 12*/ new Goods(160) /* 13*/ }; /* 14*/ for (i = 0; i < goods.length; i++) { /* 15*/ if (goods[i] instanceof TaxFreeGoods) { /* 16*/ tfg = (TaxFreeGoods) goods[i]; /* 17*/ total = total + tfg.getSalesPrice(); /* 18*/ } else if (goods[i] instanceof HalfPriceGoods) { /* 19*/ hpg = (HalfPriceGoods) goods[i]; /* 20*/ total = total + hpg.getSalesPrice(); /* 21*/ } else { /* 22*/ g = goods[i]; /* 23*/ total = total + g.getSalesPrice(); /* 24*/ } /* 25*/ } /* 26*/ System.out.println(total); /* 27*/ } /* 28*/ }
実は、このプログラムでは、
instanceof
もキャストも必要ありません。
式の値がどのクラスのインスタンスかを調べ、それに応じたインスタンス・メソッドを呼び出すことは、自動的に行われるのです。
この仕組みは
メソッド探索
(
method search
)
と呼ばれます。
このプログラムでは、単に式
goods[i].getSalesPrice()
を書くだけで、 goods [ i ]の値がクラス TaxFreeGoods のインスタンスならば、そのクラスのインスタンス・メソッド getSalesPrice が呼び出され、 HalfPriceGoods のインスタンスならばそのクラスのインスタンス・メソッドが呼び出され、 Goods のインスタンスならばそのクラスのインスタンス・メソッドが呼び出されます。
/* 1*/ class GoodsMain { /* 2*/ public static void main (String[] args) { /* 3*/ int i, total = 0; /* 4*/ Goods[] goods = { /* 5*/ new Goods(100), /* 6*/ new TaxFreeGoods(50), /* 7*/ new Goods(140), /* 8*/ new HalfPriceGoods(120), /* 9*/ new Goods(160) /* 10*/ }; /* 11*/ for (i = 0; i < goods.length; i++) { /* 12*/ total = total + goods[i].getSalesPrice(); /* 13*/ } /* 14*/ System.out.println(total); /* 15*/ } /* 16*/ }
カラオケ店の料金を計算するプログラムを作成してください。 このカラオケ店では、一般客( Singer )は1時間500円です。 ただし、子供客( ChildSinger )は半額になります。 今、1時間歌った一般客が1人、2時間歌った一般客が2人、1時間歌った子供客が1人、2時間歌った子供客が1人いたとします。 以下のプログラムに、 ChildSinger のクラス定義を追加して、合計料金を計算してください。 クラスを定義するとは、継承を利用してください。
/* 1*/ class Singer { /* 2*/ int hour; /* 3*/ Singer (int hour) { /* 4*/ this.hour = hour; /* 5*/ } /* 6*/ int getHour () { /* 7*/ return this.hour; /* 8*/ } /* 9*/ int getPrice () { /* 10*/ return this.getHour() * 500; /* 11*/ } /* 12*/ }
/* 1*/ class SingerMain { /* 2*/ public static void main (String[] args) { /* 3*/ int i, total = 0; /* 4*/ Singer[] singers = { /* 5*/ new Singer(1), /* 6*/ new Singer(2), /* 7*/ new Singer(2), /* 8*/ new ChildSinger(1), /* 9*/ new ChildSinger(2) /* 10*/ }; /* 11*/ for (i = 0; i < singers.length; i++) { /* 12*/ total = total + singers[i].getPrice(); /* 13*/ } /* 14*/ System.out.println(total); /* 15*/ } /* 16*/ }
asiaa1:~/comp3b b08a001$ java SingerMain 3250 asiaa1:~/comp3b b08a001$
今日の演習11の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b08a001@cis.twcu.ac.jpなど)とし、宛先はkonishi@cis.twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(12月12日)を明記してください。