ITエンジニアのプレイングマネージャー化応援サイト

10.継承(拡張)

この記事では、当社 の新人エンジニア研修の参考にJava8を解説します。

前回はインスタンスの活用について解説しました。

今回は継承(拡張)について解説します。

継承はとても巧妙な仕組みです。

ただし、少し直感的に分かりにくいところがあります。

手続き指向のプログラムではソースコードをざっと読めばどのような処理をしているかが一目瞭然なのですが、

オブジェクト指向では一見、自分のクラスが持っていないメソッドを使うことができたりするのです。

そのあたりのところ今回はお話ししたいと思います。

 

1.すべてのクラスのスーパークラスObject

今までにこの研修ではいくつかのクラスが出てきました。

System

String

Math

などがありましたね。

いずれもAPIを参照すると上部に以下のように記述されています。

クラスSystem

この記述が言わんとしているのはSystemクラスにはObjectクラスというスーパークラス(親クラス)がいますよ、ということです。

そして、親の持っているフィールドやメソッドはサブクラス(子クラス)が使うことができるのです。

しかし、その逆はありません。

これが継承です。

親の持っているものを子供は使える。

子供の持っているものを親は使えない。

なんだか身につまされませんか?

 

Objectクラスはその名の通り”モノ”を表すクラスです。

ObjectクラスはJavaのすべてのクラスのスーパークラスなのです。

Objectクラスには例えばequalsメソッドが定義されていますね。

したがってすべてのクラスでequalsメソッドが使えるのです。
※ただし、オーバーライドという仕組みによりスーパークラスの処理内容とサブクラスの処理の内容が違う場合がある。(後述)

 

2.継承(拡張)とは

このような、すでにあるクラスのフィールドやメソッドを新しいクラスが引き継ぐことを継承といいます

英語ではinheritanceで継承や相続といった意味があります。

継承を使うことで機能の拡張ができることから拡張と呼ばれることもあります。

拡張は英語でextendです。

この継承の仕組みは皆さんも利用することが可能です。

ただし、何でもかんでもクラス間に継承関係をつくってフィールドやメソッドを取り込めばよいかというとそれは違います。

そこには論理的必然性が必要になります。

そうでないと体系がこんがらがってしまいます。

 

継承関係を作る際の基準としては、

サブクラスは一種のスーパークラスである

と言えるかどうかが重要です。

例えば、

車は一種の乗り物です。

飛行機は一種の乗り物です。

船は一種の乗り物です。

乗り物は車、飛行機、船のスーパークラスです。

サブクラスは一種のスーパークラスであるというのを簡潔に「is-a」関係といいますので覚えておいてください。

A car is a Vehicle.

An airplane is a Vehicle.

A ship is a Vehicle.

というわけですね。

 

スーパークラスには共通のメソッドを定義して、サブクラスにそれを拡張できるようにします。

共通のメソッドの例としては、例えば、

前に進む

止まる

人を乗せる

等が考えられるでしょう。

皆さんも身の回りで継承関係が作れそうなオブジェクトの例を考えてみてください。

 

親子関係を宣言するにはextendsというキーワードを使用します。

extendsという英語には拡張するという意味があります。

※なお、Objectクラスのみはextendsキーワードなしに継承されます。すべてのクラスが自動的にObjectクラスのサブクラスや孫クラスになることが保証されています。

以下のサンプルプログラムを見てください。

<結果>

I’m imai. I’m a Java programmer.

Employeeクラスのフィールドはidとname。

Engineerクラスで宣言したフィールドはskill。

しかし、EngineerクラスはEmployeeクラスをextendsしているため3つのフィールドを持っているのです。

今回のように同一のファイルに親子両方のクラスが書かれていれば良いのですが、

そうでない場合は、ぱっと見はクラスの持っているフィールドやメソッドが分かりにくいですね。

そこで登場するのが以下のようなクラス図というものです。

クラス図ではクラスを四角形で表します。

四角形を3つに区切って、一番上がクラス名、真ん中がフィールド、一番下がメソッドです。

今回の2つのクラスではメソッドがありませんので空欄になっています。

そして、2つのクラスの関連である継承関係を白抜きの実線矢印で表現します。

このクラス図により、Engineerクラスには3つのフィールドがあることが明らかになります。

このあともいろいろなパターンが出てきますのでその都度ご紹介しますね。

 

継承のメリットは以下の2点です。

1.サブクラスをスーパークラスの変数で扱える

2.サブクラスはスーパークラスの差分だけをプログラミングすればよい。
※これを差分プログラミングといいます。

以下、順に説明します。

 

3.継承のメリット1 サブクラスをスーパークラスの変数で扱える

Javaでは、すべてのクラスのスーパークラスにはObjectクラスがあるのでした。

つまり、すべてのクラスにはObjectクラスのメソッドがあるということになります。

書いてないけれど使える。

それが継承(拡張)の仕組みでした。

では、Objectクラスを覗いてみることにしましょう。

例えば、IDEでObjectと記述してコントロールキーを押しながらその文字列をクリックしてみてください。

そして以下のようなequalsメソッドが定義されているところを探してください。

public boolean equals(Object obj) {
  return (this == obj);
}

※私の環境下では、148行目でした。

これまでの知識でこのメソッドを読み解いてみましょう。

戻り値は true or false の boolean です。

仮引数は Object obj となっています。

これはつまり、Object なら何でもという意味です。

サブクラスをスーパークラスの変数で扱えるからできることですね。

 

そして、

this == obj

thisというのはこのクラスがインスタンス化したときの自分自身を意味しています。

また、インスタンス化されていない状態でコードが書かれているため名前がなくthisと呼ばれるわけです。

 

これは参照が同一かどうかを見ているわけですね。

Objectの同一性は参照が同じであるということで判断しているということです。

さらに、Stringクラスでは独自のequalsを定義してそちらを優先させていたというのは7.文字と文字列の扱いでご紹介しました。

 

例えば以下のサンプルコードを見てください。

<結果>

I’m running.

vehicle(乗り物)クラスとCarクラスがあり、親子関係があります。

車は一種の乗り物と言えますか?

そうですね。

では、車に向かって「乗り物よ!動け!」ということはできるでしょうか?

論理的に間違っているところはありませんね。

そのようなことをしているのが上記のコードの13,14行目なのです。

つまり、

1.スーパークラスをスーパークラスの変数で扱える

ということになります。

念のためクラス図も書いておきます。

 

 

ただし、次のようなプログラムは実行できませんので注意しましょう。

今、乗り物クラスからrunメソッドを取り去りました。

このプログラムは、あたかも「乗り物よ飛べ!」といっているようなものです。

「おれ中身は飛行機だけれど、今は乗り物って呼ばれてるから飛べないんだよな」

という声が聞こえそうですね。

では、一度乗り物型の変数に紐づいた飛行機インスタンスを飛ばすにはどうしたらいいでしょうか?

そんな時は基本型でみたキャストを使います。

ただし、厳密にはインスタンスをキャストしてるのではなく、参照をキャストしてるのですが。

以下のプログラムを見てください。

<結果>

I’m flying.

このような型変換ができるということは、この後のStringクラスのequalsメソッドを読み解く際にも必要になりますので、覚えておいてください。

 

4.継承のメリット2スーパークラスはスーパークラスの差分だけをプログラミングすればよい

このことをStringクラスのequalsメソッドを使って説明しましょう。

以下は、IDEを使って覗いたStringクラスのequalsメソッドです。

※私の環境では976行目でした。
※1 instanceofは、名前の通りanObjectがStringのインスタンスかどうかを判定する演算子です。
※2 キャストを使っています。
※3 valueは、インスタンスの文字列が格納されているcharの配列(の変数)です。
※余談ですが、APIのメソッドはいずれも短いですね、このequalsメソッドがかなり長く感じられるほどです。このように一つ一つのメソッドは適切な短さを保つようにしてください。適切なメソッド名が付けられなくなったら要注意です。

4行目までは Object クラスと同じ(趣旨)ですが、そこから下が違いますね。

まだ学んでいないことが使ってありますが、char型の配列で一文字ずつ比較して、同じ文字列かどうかを判定しているのがお分かりになりますか?

Stringクラスのところで同一性と同値性のお話をしました。

同一性は、複数の変数が同じインスタンスを参照していて同じ、という意味。

同値性は、複数の変数が同じインスタンスを参照していなくても、同じ値を持っていて同じ、という意味。

でした。

Stringのequalメソッドは同値性のチェックというのはこういうことだったのですね。

 

そもそも、何をもって同じとするかは非常に難しい問題です。

私とあなたは同じでしょうか?

もちろん人間という点では同じですが、年齢、性別、身長体重など違っていることは多いと思います。

したがってクラスごとにequalメソッドの内容は書き換えることができるのです。

 

equalメソッドの例で明らかなように、スーパークラスでスーパークラスのメソッドを変更することができます。

これをオーバーライドと言います。

英語ではover(上に)ride(載)せるということで優先させるという意味です。

スーパークラスのオーバーライドされたメソッドが上書き(overwrite)されてしまったわけではないので気をつけてください。

したがってスーパークラスのメソッドをサブクラスから呼び出すことも可能です。

また、オーバーロード(多重定義:引数の数や型が異なり名前が同じメソッドを宣言すること)と名前が似ているので注意してください。

 

5.メソッドのオーバーライド

先のコードを少し書き換えてみましょう。

runメソッドをオーバーライドしてみます。

<結果>

I’m driving.

では、このことがどのように役立つのでしょうか?

それは、乗り物の変数ですべての種類の乗り物(車、飛行機、船)が扱えるということです。

次のようなプログラムが書けるということになります。

<結果>

今回のように「v.run()」という同じ命令にもかかわらず、インスタンスごとに違った処理を実行しました。

このようなオブジェクト指向の仕組みをポリモーフィズム(多態性)といいます。

 

次に、オリジナルなequalsメソッドの実装を試してみましょう。

自転車クラスを考えます。

この自転車クラスは防犯登録番号(registryNumber)が同じなら、同一であると判断するとします。

先ほどのStringクラスのequalsメソッドを参考にしてみました。

他の乗り物クラスでもequalsメソッドを持つことを想定し、汎用性を考えて仮引数を(Vehicle aVehicle)としてみました。

※ここで一見無関係に見えるhashCodeをオーバーライドしてることを不思議に思う人もいるかもしれません。実は、equalsメソッドをオーバーライドしたときにはhashCodeメソッドもオーバーライドしないと、後に学ぶHashMap、HashSetで使った際に深刻なバグを生む可能性があるのです。ちなみにこのハッシュ値はいわゆる情報セキュリティなどで出てくるハッシュ値のような一方向性や不可逆性といったことまでは要求されませんから単純に防犯登録番号を返す仕様としています。

<結果>

false
true
true

この例のようにメソッドの仮引数をスーパークラスにすることで、サブクラスの実引数を受け取れるようにしておくことをポリモーフィズムを使ったメソッドの引数設計と呼ぶことがあります。

 

6.ポリモーフィズムのメリット

ここで、ポリモーフィズムを使わずにコーディングしたとします。

何が不都合なのでしょうか?

ポリモーフィズムを使わない場合は、クラスの種類に応じて呼び出すインスタンスメソッドが変わります。

そのためif文などで処理を分岐させる必要があります。

クラスの種類に応じてというところは、instanceof演算子を使えばいいです。

こんな風になりますね。

<結果>

I’m driving.
I’m flying.
I’m driving.
I’m driving.
I’m flying.
想定していないクラスです

コード量は増えるものの何とかできました。

しかし、ここで新たにYachtクラスを作るとします。

すると、mainメソッドの中まで書き換える必要が出てきてしまうのです。

ポリモーフィズムを使った場合は乗り物の種類が増えてもmainメソッドの中身を書き換えなくて済みました。

今回は簡単なコードなので良いのですが、もっと複雑なコードで、しかも、mainメソッドの処理が現在現役で使われているクラスだったとします。

今動いているプログラムをいじるというのはとても勇気のいることです。

というわけで、ポリモーフィズムを利用するのです。

Javaのプログラムを書いていてif文やswitch文を多用していたら、そこに不吉なにおいを嗅ぎ取ってくださいね。

 

7.toStringメソッド

では、ここで初心に戻って以下のプログラムを実行してみてください。

<結果>

Bicycle@15db9742

@の前がクラス名、後ろがハッシュコードと呼ばれる16進数です。

このとき、例えば、printlnメソッドを使い防犯登録番号を表示させることはできるのでしょうか?

上記の表示を以下のように変えたいとします。

私の防犯登録番号は、1234

では、IDEでprintlnメソッドの定義をさかのぼりましょう。

クラスは、PrintStreamクラスです。

2行目でStringクラスのvalueOfというクラスメソッドを使っています。これは、xが参照する様々なオブジェクトを、String型に変換するメソッドです。

※ちなみに、printメソッドとnewLineメソッドも見えていますね。
※synchronized (this) という記述は排他制御のためです。このsynchronizedブロックで囲まれた2行は、1インスタンスごとに必ず1つのスレッドからしか実行されないため、同時アクセスによって矛盾した表示がされないことが保証されています。

では、IDEでvalueOfメソッドの定義をさかのぼりましょう。飛び先のクラスはStringです。

obj == nullでnull判定をして、nullであれば文字列”null”を、そうでなければ、toStringメソッドを呼んでいます。

では、IDEでtoStringメソッドの定義をさかのぼりましょう。

クラスはObjectクラスです。

ここでやっとご本尊(?)らしき記述と対面することができました。

では、このtoStringメソッドをオーバーライドしたらどうなるでしょうか?

オリジナルも文字表現で標準出力できそうですね。

やってみましょう!

<結果>

私の防犯登録番号は、1234

なお、オリジナルなクラスを作成したらtoStringメソッドをオーバーライドするというのは半ば常識となっています。

そうすることでデバッグ時に表示が分かりやすくなるからです。

ただし、toStringメソッドで取得したデータを使いまわすようなプログラムは慎むべきです。

フィールドの値を取得する用途には、後でお話しするアクセサメソッドを使いましょう。

 

ちなみに、ここまで来たらhashCodeメソッドの中身も気になっている人もいることでしょう。

試しに定義を確認すると、

public native int hashCode();

となっています。

中身のないメソッドです。

そしてnative修飾子がついています。

英語でも母国語を話せる人をnativeといいますね。

コンピュータの母国語はマシン語です。

マシン語(おそらくはC言語)などで書かれているのでここまでしかJavaでは遡ることはできません。

 

ちなみに、このハッシュコードは暗号化で出てくるハッシュ関数とは直接の関りはありません。

あのハッシュ関数は元に戻せない一方向性などが求められましたが、Javaのハッシュコードにはそこまで求められていません。

ただ、インスタンスをメモリの中から素早く検索する際などに使われる数値です。

 

8.継承よりも委譲を選ぶ

先にも述べたとおり、あるクラスのある機能を取り込みたいばかりに、継承を使うというのは悪い設計です。

継承を使うとスーパークラスの変更がサブクラスに及ぶため、クラス間に依存関係が生じてしまうのです。

そんなことをしなくても、他のクラスの機能を取り込むことができます。

その方法を委譲といいます。

次のサンプルプログラムを見てください。

<結果>※キーボードからHelloと入力した場合

入力してください > Hello
Helloが入力されました

ご覧いただきたいのは13行目です。

Scannerクラスのインスタンスをフィールドにしています。

そうしておいて、このクラスの中のメソッドでScannerクラスのnextLineメソッドを呼び出しています。

このようなクラスの関係を”has-a”の関係といいます。

PcクラスはScannerクラスを持っているという訳ですね。

 

このときnextLineメソッドを使いたいがためだけにPcクラスがScannerクラスを継承するのは間違いです。(そもそもScannerクラスはfinalで宣言されているため継承できませんが)

樹形図になっているクラスの体系をぐちゃぐちゃにしてしまいます。

”has-a”関係は”is-a”関係のように多重継承の問題や、サブクラスにスーパークラスの不要なフィールドやメソッドまでをも組み込んでしまうという問題を解消することになります。

継承か委譲か迷ったら委譲を使う方が良いということは頭の片隅に置いておいてください。

 

9.ClassCastException

ここでは、クラスのキャストについて見ていきます。

サブクラスは一種のスーパークラスでした。

しかし、スーパークラスは一種のサブクラスであるとは言えません。

ですからスーパークラスのインスタンスをサブクラスのインスタンス変数(参照)に代入することはできません。

以下のサンプルプログラムを見てください。

<結果>

Exception in thread “main” java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String

この時送出されるのがClassCastExceptionという例外です。

オブジェクトは一種の文字列である、とは言えません。

そのためこのようなキャストができないという例外が発生したのでした。

 

10.多重継承の禁止

なお、Javaでは、あるクラスが持てるスーパークラスは1つのみです。

これを単一継承といいます。

単一継承であるということは、どのクラスにもスーパークラスは1つだけだということです。

しかし、スーパークラスにはサブクラスが複数いても良いことになります。

それを敷衍すると、クラスの体系は樹形図になります。

例えば、Java8のAPIで確認するとこんな感じです。

全てのクラスの階層ツリー

 

一方、複数のスーパークラスを持てる仕組みを多重継承といいます。

例えば、C++言語では多重継承を採用しています。

Javaではなぜ、多重継承を禁止したのでしょうか?

それはこういう訳です。

class A{

  show();

}

class B{

  show();

}

class C extends A,B{

}

このようなコードがあった場合に、メインメソッドの中で、

C obj1 = new C();

obj1.show();

としたらどうなるでしょうか?

このshowメソッドがクラスA由来のものかクラスB由来のものか判断がつきませんね。

そういう訳で、Javaでは多重継承が禁じられているのです。

ただし、後で学ぶインターフェースというものを使うと多重継承的な処理が可能になりますのでお楽しみに。

 

ここまで理解できたら以下の練習問題を解いてみましょう。

9.拡張とポリモーフィズム

今回は継承(拡張)について見てきました。

スーパークラスの変数で扱えることやポリモーフィズムなど便利そうな仕組みがありましたね。

ただし、「継承は最後の手段」という言葉があります。

Javaは単一継承のため、継承を使ったクラス設計はめったに採用されないと思ってください。

特に新入社員の皆さんはなおさらです。

ただし、継承関係にあるクラスを利用することは多くありますからその仕組みを理解しておくことはとても重要です。

なお、継承されないものとして以下の3つがあることは知っておくとなおよいでしょう。

①プライベート宣言されたメンバー

②コンストラクタ(メンバーでないので)

③スタティックメンバー(インスタンスの中に含まれないので)

 

次回のテーマはカプセル化です。

これも、オブジェクト指向特有のテーマです。

さらに、継承よりも身近に使われるテクニックです。

カプセル化を使ってより頑強なクラスを作っていきましょう。

 

JavaSE8の解説に戻る

新入社員研修ポータル

IT企業の人財育成に関することなら全てお任せ下さい TEL 0120-559-463 受付時間 10:00 - 17:00 (土・日・祝日除く)

ZOOMを使った遠隔研修メニュー(PDFが開きます)

ZOOMを使った遠隔研修

新人エンジニアのためのJavaタイピングゲーム

新人プログラマのためのプログラミング動画

YouTubeチャンネル

お問い合わせはこちらから

    お名前 (必須)

    メールアドレス (必須)

    題名(件名)

    メッセージ本文

    確認画面は表示されません。上記内容にて送信しますので、よろしければチェックを入れてください。

    新入社員研修ポータル

    PAGETOP
    Copyright © Say Consulting Group, Inc. All Rights Reserved.