[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

コンピュータIIIB(Javaアルゴリズム)第11回

目次
11.1 継承(2)
11.1.1 メソッドのオーバーライド
11.1.2 スーパークラスのメソッド
11.1.3 クラスの比較と確認
11.1.4 メソッド探索
11.2 演習11
11.3 レポート課題
11.4 参考文献
索引
instanceof演算子   superキーワード   オーバーライド   キャスト演算子   メソッド探索  

11.1 継承(2)

11.1.1 メソッドのオーバーライド

継承の仕組みを利用しますと、一般的なオブジェクトの変数と関数・手続きが、特殊なオブジェクトへ引き継がれます。 しかし、一部は継承してほしくないときもあります。 そのような場合は、オーバーライドと呼ばれることを行います。

ここでは、例として、商店の商品に関する次のような計算を考えます。

表 11.1  商店の商品に関する計算
番号 商品 本体価格 非課税 半額処分 販売価格
0 ボールペン 100 × × 105
1 ハガキ 50 × 50
2 ノート 140 × × 147
3 パン 120 × 63
4 ジュース 160 × × 168
合計 533

商品の性質は、本体価格が決まっていることです。 商品の機能は、本体価格が決められることと、それが取り出せることです。 消費税額と販売価格が計算できることも、商品の機能とします。

商品はクラス Goods で表すことにします。 本体価格は、インスタンス変数 price で表します。 本体価格を決めることは、インスタンス・メソッドではなく、コンストラクタで書くことにします。 本体価格を取り出すことは、インスタンス・メソッド getPrice で表します。 インスタンス・メソッド getTaxgetSalesPrice で、それぞれ消費税額と販売価格を計算することにします。 すると、クラスの定義プログラムは次のようになります。

Goods.java
/*  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 とします。 取りあえず、継承は行わないことにします。

TaxFreeGoods.java
/*  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 とします。 取りあえず、継承を利用しないでクラスを定義します。

HalfPriceGoods.java
/*  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 を利用しますと、合計金額が計算できます。

GoodsMain.java
/*  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*/ }
b04a001@AsiaA1:~/comp3b% java GoodsMain
533
b04a001@AsiaA1:~/comp3b%

非課税商品は、商品の中で特殊なものですので、 TaxFreeGoods のクラス定義は Goods のクラス定義に似ています。 したがって、継承を利用したいところですが、インスタンス・メソッド getTax の部分が違いますので、そのままでは引き継げません。 半額処分商品も特殊な商品ですので、クラス HalfPriceGoods の定義もクラス Goods の定義に似ています。 しかし、インスタンス・メソッド getSalesPrice が違いますので、そのまま継承するわけにはいきません。

このような場合、インスタンス・メソッドをオーバーライドしますと、うまく継承できます。 オーバーライドoverride ) とは、スーパークラスの定義をサブクラスで再定義することです。

インスタンス・メソッドがオーバーライドされますと、スーパークラスのインスタンスについては、スーパークラスのメソッド定義が有効になります。 サブクラスのインスタンスについては、サブクラスのメソッド定義が有効になります。 したがって、スーパークラスからそのまま引き継げないインスタンス・メソッドは、サブクラスでオーバーライドすればよいのです。

なお、インスタンス・メソッドをオーバーライドする場合、スーパークラスのインスタンス・メソッドとサブクラスのインスタンス・メソッドは、返り値のデータ型が一致しなければなりません。 また、コンストラクタについては特別で、定義の本体に

super(argument...);

と書きます。

TaxFreeGoods.java(第2版)
/*  1*/ class TaxFreeGoods extends Goods {
/*  2*/     TaxFreeGoods (int price) {
/*  3*/         super(price);
/*  4*/     }
/*  5*/     int getTax () {
/*  6*/         return 0;
/*  7*/     }
/*  8*/ }
HalfPriceGoods.java(第2版)
/*  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*/ }

11.1.2 スーパークラスのメソッド

インスタンス・メソッドをオーバーライドしますと、スーパークラスのインスタンス・メソッドは隠されてしまいます。 しかし、スーパークラスのメソッド定義とサブクラスのメソッド定義は、大抵の場合は似ています。 もし、サブクラスのメソッド定義でスーパークラスのインスタンス・メソッドが呼び出せるのなら、メソッド定義においても違いのみを書けばよくなります。

キーワード super を用いますと、サブクラスのメソッド定義でスーパークラスのインスタンス・メソッドが呼び出せます。 具体的には、

super.methodname(argument...)

と書きます。

クラス HalfPriceGoods の場合ですと、インスタンス・メソッド getSalesPrice の定義がスーパークラス Goods のものと似ています。 そこで、キーワード super を使ってスーパークラスのインスタンス・メソッドを呼び出し、その値を2で割るだけにします。

HalfPriceGoods.java(第3版)
/*  1*/ class HalfPriceGoods extends Goods {
/*  2*/     HalfPriceGoods (int price) {
/*  3*/         super(price);
/*  4*/     }
/*  5*/     int getSalesPrice () {
/*  6*/         return super.getSalesPrice() / 2;
/*  7*/     }
/*  8*/ }

11.1.3 クラスの比較と確認

このプログラムの難点は、商品が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 演算子とキャスト演算子は、しばしばセットで使われます。

GoodsMain.java(第2版)
/*  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*/ }

11.1.4 メソッド探索

実は、このプログラムでは、 instanceof もキャストも必要ありません。 式の値がどのクラスのインスタンスかを調べ、それに応じたインスタンス・メソッドを呼び出すことは、自動的に行われるのです。 この仕組みは メソッド探索method search ) と呼ばれます。

このプログラムでは、単に式

goods[i].getSalesPrice()

を書くだけで、 goods [ i ]の値がクラス TaxFreeGoods のインスタンスならば、そのクラスのインスタンス・メソッド getSalesPrice が呼び出され、 HalfPriceGoods のインスタンスならばそのクラスのインスタンス・メソッドが呼び出され、 Goods のインスタンスならばそのクラスのインスタンス・メソッドが呼び出されます。

GoodsMain.java(第3版)
/*  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*/ }

11.2 演習11

カラオケ店の料金を計算するプログラムを作成してください。 このカラオケ店では、一般客( Singer )は1時間500円です。 ただし、子供客( ChildSinger )は半額になります。 今、1時間歌った一般客が1人、2時間歌った一般客が2人、1時間歌った子供客が1人、2時間歌った子供客が1人いたとします。 以下のプログラムに、 ChildSinger のクラス定義を追加して、合計料金を計算してください。 クラスを定義するとは、継承を利用してください。

Singer.java
/*  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*/ }
SingerMain.java
/*  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*/ }
b04a001@AsiaA1:~/comp3b% java SingerMain
3250
b04a001@AsiaA1:~/comp3b%

11.3 レポート課題

今日の演習11の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b04a001@cis.twcu.ac.jpなど)とし、宛先はkonishi@cis.twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(12月21日)を明記してください。


11.4 参考文献


[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

2007年12月21日更新
小西 善二郎 <konishi@cis.twcu.ac.jp>
Copyright (C) 2007 Zenjiro Konishi. All rights reserved.