RAT Java勉強会2004 第04回 クラスの継承とポリモーフィズム
Javaの利点を説明する際に「継承」や「ポリモーフィズム」の説明は避けて通れません。
また、Javaの継承機能はかなりの制限があるため、それを解消するために「インターフェース」が存在します。
今回は、「継承」「ポリモーフィズム」「インターフェース」などの意味や使い方を紹介します。
- タクシーの特徴を挙げてください
タクシーの特徴 (1)
Section titled “タクシーの特徴 (1)”- 料金メーターを持つ
- 客を乗せられる
- 客を降ろせる
- お金を取って運転できる
- 無線で他のタクシーと通信できる
タクシーの特徴 (2)
Section titled “タクシーの特徴 (2)”- 車である
- 人が乗れる
- エンジンを持っている
- ハンドルを持っている
- アクセルを持っている
- ブレーキを持っている
- ハンドルを操作すると進行方向を変えられる
- アクセルを踏むと加速する
- ブレーキを踏むと減速する
タクシーの特徴抽出
Section titled “タクシーの特徴抽出”- 料金メーターを持つ - has-a 関係
- 客を乗せられる - 振る舞い
- 客を降ろせる - 振る舞い
- 賃送できる - 振る舞い
- 無線をもつ - has-a 関係
- 車である - is-a 関係
-
“is-a関係”にあるクラスは、継承によって特性を引き継げる
- Taxi is-a Car
- TaxiはCarの特徴を持つ
-
特性とは、具体的には以下
- インスタンスフィールド
- インスタンスメソッド
class Car { // Carの一般的特性 ...}class Taxi extends Car { // Taxiだけが持つ一般的特性 ...}- Tanaka is-a Human
- TanakaはHumanである(Humanの特性を持ち合わせる)
- Suzuki is-a Human
- SuzukiはHumanである(Humanの特性を持ち合わせる)
- Coffee is-a Drink
- CoffeeはDrinkである(Drinkの特性を持ち合わせる)
- Juice is-a Drink
- JuiceはDrinkである(Drinkの特性を持ち合わせる)
Objectクラス
Section titled “Objectクラス”-
Human is-a Object
-
Drink is-a Object
-
全てのクラスは”java.lang.Objectクラス”を継承している
- 必ず祖先にObjectがいる
-
Javaでは、2つのクラスを同時に継承できない
- 1つのクラスしか継承できないので”単一継承”と言う
-
次の例は、単一継承だと実装が困難である
- 携帯電話は電話機であり、メーラーでもある
- 携帯電話 is-a 電話機
- 携帯電話 is-a メーラー
- 携帯電話は電話機であり、メーラーでもある
振る舞いの上書き
Section titled “振る舞いの上書き”同じ名前の違う振る舞い
Section titled “同じ名前の違う振る舞い”-
Taxi is-a Car だけど…
- Taxiが走る(Taxi#drive)のは賃送
- Carが走る(Car#drive)のは賃送ではない
-
同じ”走る”でも、振る舞いは違う
-
Taxi extends Car
- Carからdriveは受け継いでいる
-
driveじゃなくて違う名前にするべきか?
- オブジェクト指向っぽくない
振る舞いの上書き
Section titled “振る舞いの上書き”-
Javaでは、継承した振る舞いを上書きできる
- Taxiでdriveを”再定義する”ともいえる
-
振る舞いの上書きを「メソッドのオーバーライド」とも言う
- 同じ名前でいいので、仕様を覚える手間が省ける
- 実はオブジェクト指向の最高の機能(個人的意見)
is-a関係と型
Section titled “is-a関係と型”-
Taxi is-a Car という関係より
- TaxiオブジェクトをCarオブジェクトとして使える
-
Taxi特有の機能ではなく、Carとして扱えば十分という場面で役に立つ
public void wideningCast() { // Taxi is-a Car Car car = new Taxi(); car.shiftGears(); ...}ワイドニング変換
Section titled “ワイドニング変換”- あるインスタンスを、親クラスの型として扱う変換をワイドニング変換と呼ぶ
- 全てのクラスの基底であるObjectクラスへはなんでも代入できる
- プリミティブ型はだめ
public void wideningObject() { // Taxi is-a Object Object obj = new Taxi(); // Array is-a Object Object aobj = new int[5];}ワイドニング変換のイメージ
Section titled “ワイドニング変換のイメージ”Car car = new Taxi();
- 設計図”Taxi”を工場に送って作ってもらう
- 作ってもらった製品を世界のどこかに置く
- 置いた場所と型(Taxi)を記録した紙を作成する
- その紙の型を”Car”に書き換える
- Taxi is-a Carなので書き換え可能
- “Car型”の箱”car”へ作成した紙を入れる
ナローイング変換
Section titled “ナローイング変換”- ワイドニング変換の逆向きに型を変換しようとすることを、ナローイング変換という
- 普通、これは禁止されている
- タクシーは車であるが、車はタクシーであるとは限らない
public void narrowingCoversion() { // o Taxi is-a Car Car car = new Taxi(); // x Car is-a Taxi Taxi taxi = car;}暗黙ナローイング変換失敗のイメージ
Section titled “暗黙ナローイング変換失敗のイメージ”Taxi taxi = car;
- “Car型”の箱”car”から紙を取り出す
- その紙の型を”Taxi”に書き換える
- ここで、Car is-a Taxi が成り立たないので失敗
Explicitな型キャスト
Section titled “Explicitな型キャスト”- ナローイング変換は明示的に型のキャストを行うことで可能になる。
- 型キャストに失敗すると、ClassCastExceptionがスローされる
public void downCast() { // Taxi is-a Car Car car = new Taxi(); // Car can be a Taxi Taxi taxi = (Taxi) car;}Explicitな型キャストのイメージ
Section titled “Explicitな型キャストのイメージ”Taxi taxi = (Taxi) car;
- “Car型”の箱”car”から紙をコピーしてくる
- その紙の型が”Taxi”に変換できるか調べに行く
- Explicitなキャストは、実際に現地へ調べにいく
- その紙の型を”Taxi”に書き換える
- “Taxi型”の箱”taxi”へその紙を入れる
instanceof式
Section titled “instanceof式”- インスタンスがそのクラスから生成されたかどうか調べられる
- これを多用するプログラムはオブジェクト指向らしくない
public void isInstanceOf() { // Taxi is-a Object Object obj = new Taxi();
// print this obj is instanceof Taxi? System.out.println(obj instanceof Taxi);}Polymorphism
Section titled “Polymorphism”犬も猫も動物 (1)
Section titled “犬も猫も動物 (1)”- 動物は鳴く
class Animal { public void sing() { }}犬も猫も動物 (2)
Section titled “犬も猫も動物 (2)”- 犬は動物である
- 犬はBowwowと鳴く(表示する)
class Dog extends Animal { public void sing() { System.out.println("Bowwow"); }}犬も猫も動物 (3)
Section titled “犬も猫も動物 (3)”- 猫は動物である
- 猫はMewと鳴く(表示する)
class Cat extends Animal { public void sing() { System.out.println("Mew"); }}動物の振る舞い? (1)
Section titled “動物の振る舞い? (1)”- 次の文章をプログラムに変換してみましょう
“犬という動物がいる。その動物は鳴いた。”
“猫という動物がいる。その動物は鳴いた。“
動物の振る舞い? (2)
Section titled “動物の振る舞い? (2)”class MainDog { public static void main(String[] args) { // 犬という動物がいる。… Animal animal = new Dog();
// …その動物は鳴いた。 animal.sing(); }}動物の振る舞い? (3)
Section titled “動物の振る舞い? (3)”class MainCat { public static void main(String[] args) { // 猫という動物がいる。… Animal animal = new Cat();
// …その動物は鳴いた。 animal.sing(); }}動物の振る舞い? (4)
Section titled “動物の振る舞い? (4)”- それぞれのanimal.sing()の挙動を考えてみましょう
Polymorphism
Section titled “Polymorphism”-
同じメソッド呼び出しに対して、異なるオブジェクトが異なる操作をすること
- 対象の振る舞いの詳細を気にせず、名前だけで呼べばよい
-
“その動物は鳴いた” とだけあって、動物が何かを気にしない
- 犬ならば勝手に “Bowwow” と鳴く振る舞いをする
- 猫ならば勝手に “Mew” と鳴く振る舞いをする
Polymorphismを使った例
Section titled “Polymorphismを使った例”“犬が2匹、猫が1匹いる。3匹の動物達はそれぞれ鳴いた。“
class AnimalEnsemble { public static void main(String[] args) { // 3匹の動物達 Animal[] animals = new Animal[] { // 犬が2匹 new Dog(), new Dog(), // 猫が1匹 new Cat() }; // 3匹の動物達はそれぞれ… for (int i = 0; i < animals.length; i++) { // 鳴いた animals[i].sing(); } }}Polymorphismのイメージ
Section titled “Polymorphismのイメージ”- 実際に絵を描いてみると分かりやすい
- 第02回のメソッド呼び出しを参照

Polymorphismの利点
Section titled “Polymorphismの利点”-
オブジェクトの実装を気にせずに使用できる
- 相手の振る舞いは相手に任せる
- 相手が変わると振舞いも変わる
-
処理を一般化できる
- 覚えることが少なくてすむ
- “動物は鳴く”というカテゴリの振る舞いのみを知っていれば良い
-
先ほどのプログラムを考えてみる
- 犬という実体は世界に存在した
- 猫という実体は世界に存在した
- 動物という実体は?
-
犬も猫も動物だが、“動物”そのものはいない
抽象的な存在
Section titled “抽象的な存在”-
犬や猫は存在する
- 具体的なオブジェクトを表している
-
動物そのものは存在していない
- 犬や猫というカテゴリを階層化するための”概念”
- 抽象的な存在 (動物はいないが、動物である犬はいる)
概念の実体化
Section titled “概念の実体化”- 動物は抽象的なクラス(存在ではなく概念)であるといえる
- しかし、このままでは具象化した存在になれる
public void concretise() { // 動物を作成 Animal animal = new Animal(); ...}抽象クラスの定義
Section titled “抽象クラスの定義”- Javaでは抽象的なクラスをプログラムで表現できる
- インスタンス化できない
- 継承されて始めて意味を持つ
abstract class Animal { public void sing() { }}抽象メソッド
Section titled “抽象メソッド”- 振る舞いを実際に持たない、名前だけのメソッド
- 抽象クラスは抽象メソッドを持つことができる
- サブクラスで必ずオーバーライドしなければならない
- Animalでsingという振る舞いを定義することはできない
- DogやCatで定義することはできる
- Animalが持っていないと、Animal型から使えない
abstract class Animal { abstract public void sing();}抽象クラスと骨格実装
Section titled “抽象クラスと骨格実装”-
抽象クラスは通常のメソッドと抽象メソッドを混在させられる
- カテゴリに共通の処理を抽象クラス内で記述
- サブクラス特有の処理を抽象メソッドにする
-
サブクラスで特有の処理だけを書くことによって、オブジェクトを定義できる
- このような実装を”骨格実装”という
-
オブジェクト指向プログラムの美しさは、ここに掛かっている
オブジェクトの関連性
Section titled “オブジェクトの関連性”-
喫茶店とコーヒーメーカーの関連性はなんでしょうか
- 喫茶店 is-a 店
- コーヒーメーカー is-a 機械
-
現実世界のカテゴリで考えると、関連性はなさそうに見える
共通する振る舞い
Section titled “共通する振る舞い”-
喫茶店とコーヒーメーカーで共通する関連性はなんでしょうか
- 喫茶店はコーヒーを出す
- コーヒーメーカーはコーヒーを出す
-
振る舞いだけ見ると”コーヒーを出す”という共通点がある
インターフェース
Section titled “インターフェース”-
いくつかのクラスが共通の振る舞いを持つ場合、それらは共通のインターフェースを持つ
- “コーヒーを出すもの” というインターフェース
-
このインターフェースをJavaで記述することができる
インターフェースの記述
Section titled “インターフェースの記述”- “コーヒーを出すもの”というインターフェース
interface CoffeeProvider { // コーヒーを出す作業を持っている Coffee getCoffee();}インターフェースの使用 (CoffeeMaker)
Section titled “インターフェースの使用 (CoffeeMaker)”- CoffeeMakerはCoffeeProviderを実際に”実装している” (コーヒーを出すものとして振る舞える)
class CoffeeMaker implements CoffeeProvider { ... public Coffee getCoffee() { return new Coffee(); }}インターフェースの使用 (CafeShop)
Section titled “インターフェースの使用 (CafeShop)”- CafeShopはCoffeeProviderを実際に”実装している” (コーヒーを出すものとして振る舞える)
class CafeShop implements CoffeeProvider { // CafeShop has-a コーヒーを作る手段 private CoffeeProvider coffeeMaker = ...
public Coffee getCoffee() { return coffeeMaker.getCoffee(); }}インターフェースのis-a関係
Section titled “インターフェースのis-a関係”-
“コーヒーを入れるもの”という機能でカテゴライズしたが、is-a関係が成り立っている
- CafeShop is-a CoffeeProvider
- CoffeeMaker is-a CoffeeProvider
-
あとは、普通の親クラスのようにワイドニング変換して使える
インターフェースの使用例
Section titled “インターフェースの使用例”class Caffine { public static void main(String[] args) { // コーヒーを提供できる何か CoffeeProvider cc = new CafeShop();
// 実際に生成 Coffee coffee = cc.getCoffee(); }}インターフェースの意味
Section titled “インターフェースの意味”-
インターフェースを持っているということ
- 「最低限、この機能だけは使える」という指標
-
違うカテゴリにいるけど、共通する”界面”を持つということ
- ここで、界面とは振る舞いのことである
抽象クラス vs. インターフェース
Section titled “抽象クラス vs. インターフェース”-
抽象クラス
- 1つしか継承できない
- 骨格実装ができる
-
インターフェース
- いくつでも継承できる
- 具象メソッドを持てない
-
状況によって使い分けることが大切
続々・オブジェクト指向プログラミング
Section titled “続々・オブジェクト指向プログラミング”- 次の文章をプログラムにしてみましょう
“喫茶店でコーヒーを注文し、Tanakaはそれを飲んだ。喫茶店でジュースを注文し、Suzukiはそれを飲んだ”
オブジェクトの抽出
Section titled “オブジェクトの抽出”- 喫茶店
- コーヒー
- Tanaka
- ジュース
- Suzuki
オブジェクトの階層化
Section titled “オブジェクトの階層化”-
カテゴリが近いと思われるオブジェクトを探す
-
喫茶店
-
コーヒー、ジュース
-
Tanaka、Suzuki
上位クラスの作成
Section titled “上位クラスの作成”-
共通点を洗い出して上位クラスを作る
-
喫茶店 (“店”を上位に持ってきても良い)
-
飲み物
- コーヒー (Coffee is-a Drink)
- ジュース (Juice is-a Drink)
-
人
- Tanaka (Tanaka is-a Human)
- Suzuki (Suzuki is-a Human)
振る舞いの抽出
Section titled “振る舞いの抽出”-
階層を考えずに、オブジェクトに振る舞いを結びつける
-
喫茶店
- コーヒーを注文される (getCoffee())
- ジュースを注文される (getJuice())
-
Tanaka
- コーヒーを飲む (drink(Coffee))
-
Suzuki
- ジュースを飲む (drink(Juice))
振る舞いの一般化
Section titled “振る舞いの一般化”-
振る舞いの引数を一般化する
- コーヒーを飲む → 飲み物を飲む
- ジュースを飲む → 飲み物を飲む
-
Tanaka
- 飲み物を飲む (drink(Drink))
-
Suzuki
- 飲み物を飲む (drink(Drink))
振る舞いの引き上げ
Section titled “振る舞いの引き上げ”-
親クラスに引き上げられる振る舞いを探す
-
Human#drink(Drink)
- Tanaka#drink(Drink)
- Suzuki#drink(Drink)
-
ここで、TanakaやSuzukiが特殊な飲み方をするなら、オーバーライドすればよい
残りのプログラム
Section titled “残りのプログラム”- あとは今までの流れと同様にすればよい
課題1 - 継承による一般化
Section titled “課題1 - 継承による一般化”-
次の文章は第3回で使用した例題である。 “Tanakaは5000円持った状態でタクシーに乗った。タクシーは乗客が乗ると料金メーターをリセットし、1km走るごとに200円ずつ課金する。Tanakaはその車で10km走り、その後に降りた。タクシーは乗客が降りる際に課金された金額を乗客に請求する。”
-
継承を使って再利用可能にせよ
- Tanaka is-a 乗客
- 「“Tanaka”という名前を持つ5000円持った状態にある乗客」としても良い
- タクシー is-a 車
- Tanaka is-a 乗客
-
どのクラスにどの振る舞いがあるか注意せよ
課題2 - プログラムの再利用
Section titled “課題2 - プログラムの再利用”- 課題1で作成したプログラムを流用し、次の文章をプログラムに変換せよ
- ただし、課題1のプログラムを変更してはならない
“Satoは10000円持った状態でタクシーに乗った。タクシーは乗客が乗ると料金メーターをリセットし、1km走るごとに200円ずつ課金する。Satoはその車で18km走り、その後に降りた。タクシーは乗客が降りる際に課金された金額を乗客に請求する。“
課題3 - Composite
Section titled “課題3 - Composite”- 多重継承ができないJavaでは”携帯電話”は表現しにくかった
- 次のように考えてみてはどうか
- 電話機は電話できるというインターフェースを持つ
- メーラーはメールできるというインターフェースを持つ
- 携帯電話は電話機を内部に持っている (has-a)
- 携帯電話はメーラーを内部に持っている (has-a)
- 携帯電話は電話できるというインターフェースを持つ
- 携帯電話はメールできるというインターフェースを持つ
- これらのオブジェクトの関連性を説明せよ