新人エンジニア研修で知っておきたい継承の使い方
なぜ、継承の理解が重要なのか、その理由。
この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。
前回はインスタンスの活用について解説しました。
今回は継承(拡張)について解説します。
継承はとても巧妙な仕組みです。
ただし、新人エンジニアの皆さんには少し直感的に分かりにくいところがあります。
手続き指向のプログラムではソースコードをざっと読めばどのような処理をしているかが一目瞭然なのですが、
オブジェクト指向では一見、自分のクラスが持っていないはずのメソッドを使うことができたりするのです。
目次
1.すべてのクラスのスーパークラスObject
今までにこの研修ではいくつかのクラスが出てきました。
前回のインスタンスの活用で作成したオリジナルクラスNewEngineerもありました。
標準APIに用意されていた
System(標準API)
String(標準API)
Math(標準API)
などもそうですね。
Javaの標準APIを参照すると先頭部分に以下のように記述されています。
以下は、Systemクラスの例です。
クラスSystem
- java.lang.Object
- java.lang.System
この記述が言わんとしているのはSystemクラスにはObjectクラスというスーパークラスがいますよ、ということです。
そして、スーパークラスの持っているメンバ(フィールドやメソッド)はサブクラスが使うことができるのです。
しかし、その逆はありません。
人間も先祖から遺伝情報をもらっていますね。
あなたの特徴やスキルにもご自身で獲得したものと、先祖から伝わっているものがあるかと思います。
先祖から伝わっているものを使えるのが継承の仕組みです。
親(先祖)の持っているものを子供は使える。
子(子孫)の持っているものを親は使えない。
なんだか身につまされませんか?
※なお、本書では直接継承関係のあるクラスを親クラス、子クラスと表現することがありますのでご了承下さい。
Objectクラスはその名の通り"モノ”を表すクラスです。
ObjectクラスはJavaのすべてのクラスのスーパークラスなのです。
Objectクラスには例えばequalsメソッドが定義されていますので標準APIで確認して下さい。
よって、すべてのクラスでequalsメソッドが使えるのです。
ただし、オーバーライドという仕組みにより同じ名前のメソッドであってもスーパークラスの処理内容とサブクラスの処理の内容が違う場合があります。
2.継承(拡張)とは
継承とは、すでにあるクラスのフィールドやメソッドを新しいクラスが引き継ぐことをいいます。
英語では“inheritance”で「継承や相続」といった意味があります。
継承を使うことで機能の拡張ができることから拡張と呼ばれることもあります。
拡張は英語で“extends”です。
皆さんは髪の毛のエクステを知っていますか?
それがextendsのイメージです。
この継承の仕組みは皆さんも利用することが可能です。
ただし、何でもかんでもクラス間に継承関係をつくってフィールドやメソッドを取り込めばよいかというとそれは違います。
そこには論理的必然性が必要になります。
そうでないと体系がこんがらがってしまいます。
継承関係を作る際の基準としては、
サブクラスは一種のスーパークラスである
と言えるかどうかが重要です。
例えば、
車は一種の「乗り物」です。
飛行機は一種の「乗り物」です。
船は一種の「乗り物」です。
よって「乗り物」は車、飛行機、船のスーパークラスです、という具合に。
サブクラスは一種のスーパークラスであるというのを簡潔に「is-a」関係といいますので覚えておいてください。
A car is a Vehicle.
An airplane is a Vehicle.
A ship is a Vehicle.
というわけですね。
スーパークラスには共通のメソッドを定義して、サブクラスにそれを拡張できるようにします。
乗り物クラスに共通のメソッドの例としては、例えば、
- 進む
- 止まる
- 人を乗せる
等が考えられるでしょう。
皆さんも身の回りで継承関係が作れそうなオブジェクトの例を考えてみてください。
親子関係を宣言するにはextendsというキーワードを使用します。
extendsという英語には拡張するという意味があるのでしたね。
以下の3つのサンプルプログラムを見てください。
<結果を表示する>
私は今井。私のスキルは Java です。
Employeeクラスのフィールドはidとname。
Engineerクラスで宣言したフィールドはskill。
EngineerクラスはEmployeeクラスをextendsしているため3つのフィールドを持っているのです。
しかし、ぱっと見はクラスの持っているフィールドやメソッドが分かりにくいですね。
そこで登場するのが下図10.1のようなクラス図というものです。

クラス図ではクラスを四角形で表します。
四角形を3つに区切って、一番上がクラス名、真ん中がフィールド、一番下がメソッドです。
今回の2つのクラスではメソッドがありませんので空欄になっています。
そして、2つのクラスの関連である継承関係を白抜きの実線矢印で表現します。
クラス図の矢印の向きは重要です。
なぜなら、依存関係を表現するからです。
上記の例で言えば、親クラスが無いと子クラスが成立しません。
一方、子クラスがなくても親クラスは成立します。
なお、Objectクラスのみはextendsキーワードなしに継承されます。
すべてのクラスが自動的にObjectクラスのサブクラスや孫クラスになることが保証されていますのでわざわざクラス図で表現しません。
さらに詳細説明は研修の目的に応じて講師からいたします。
このクラス図により、Engineerクラスには3つのフィールドがあることが明らかになります。
このあともいろいろなパターンが出てきますのでその都度ご紹介しますね。
継承のメリットは以下の2点です。
1.サブクラスはスーパークラスの差分だけをプログラミングすればよい。
※これを差分プログラミングといいますが現在ではこれをメリットと捉えない考え方も有力です。
2.サブクラスをスーパークラスの変数で扱える。
※これを応用してポリモーフィズムを実現します。これは明らかにメリットです。
1については上記の例で簡単に説明できましたので、2について説明します。
3.継承関係で複数のクラスを仲間として扱える
サブクラス達をスーパークラスの参照で扱うことで複数のクラスを仲間として扱うことができます。
Javaでは、すべてのクラスのスーパークラスにはObjectクラスがあります。
つまり、すべてのクラスにはObjectクラスのメソッドがあるということになります。
書いてないけれど使える。
それが継承(拡張)の仕組みでした。
では、Objectクラスを覗いてみることにしましょう。
そろそろ慣れた頃かと思いますが、講師と一緒にJavaのソースコード・ツアーに出かけましょう。
そして148行目以降のequalsメソッドが定義されているところを探してください。
public boolean equals(Object obj) {
return (this == obj);
}
これまでの知識でこのメソッドを読み解いてみましょう。
戻り値は true or false のいずれかの boolean です。
仮引数は Object obj となっています。
これはつまり、Object なら何でもという意味です。
サブクラスをスーパークラスの変数で扱えるからできることですね。
そして、
this == obj
このthisというのはこのクラスがインスタンス化したときの自分自身(の参照)を意味しています。
まだ、インスタンス化されていない状態でコードが書かれているため参照がなくthis(これ)としか表現しようがないわけです。
これは参照が同一かどうかを見ているわけですね。
Objectの同一性は参照が同じであるということで判断しているということです。
なお、Stringクラスでは独自のequalsを定義してそちらを優先させていたというのは7.文字と文字列の扱いでご紹介しました。
例えば以下の3つのサンプルコードを見てください。
<結果を表示する>
I'm running.
vehicle(乗り物)クラスとCarクラスがあり、親子関係があります。
車は一種の乗り物と言えますか?
言えますね。
では、車に向かって「乗り物よ!動け!」ということはできるでしょうか?
論理的に間違っているところはありませんね。
そのようなことをしているのが上記のコードのメインメソッドなのです。
つまり、
サブクラスをスーパークラスの参照で扱える
ということになります。
サブクラスをスーパークラスの参照で扱うことで複数のクラスを仲間として扱ったわけです。
念のため下図1.2のクラス図も書いておきます。

ただし、次のようなプログラムは実行できませんので注意しましょう。
このプログラムは、あたかも「乗り物よ飛べ!」といっているようなものです。
「おれ中身は飛行機だけれど、今は乗り物って呼ばれてるから飛べないんだよな」
という声が聞こえそうですね。
冗談はさておき、スーパークラス型の変数でサブクラスを扱う時、サブクラス独自のメソッドをそのままでは呼び出せません。
しかし、実態は飛行機なのです。
ですから、いったんは乗り物型の変数に紐づいた飛行機も、飛ばすことは可能です。
プリミティブ型でもみたキャストを使います。
以下のプログラムを見てください。
※より厳密にはインスタンスをキャストしてるのではなく、参照をキャストしている。
<結果を表示する>
I'm flying.
ただ、これだけでは、サブクラスをスーパークラスの参照で扱えることの嬉しさがあまり伝わらないかもしれません。
4.メソッドのオーバーライド(Stringクラスの場合)
オーバーライドとは、サブクラスでスーパークラスのメソッドを優先させることでポリモーフィズムを可能にします。
ポリモーフィズムとは、同じメソッドに対して違った処理をさせることです。
順を追って説明します。
まず、サブクラスをスーパークラスの参照で扱うメリットをStringクラスのequalsメソッドを使って説明しましょう。
その前にinstanceof演算子について簡単にご紹介しておきます。
この演算子はインスタンスのクラスを調べるときに有効です。
使用例です。
String s = "Hello";
System.out.println(s instanceof String);
tureと表示されます。
その上で、Stringクラスのequalsメソッドを読み解いていきましょう。
ソースコードを読む前に要件を整理しておきます。
前提:"Hello".equels("Hello")がtrueになることを検証する
文字列"Hello"と比較対象の文字列 "Hello"が同じであるということは以下の2つのパターンがあります。(下図10.3)
1.どちらの参照の中身も同じ場合(同一のインスタンスを指している場合)
2.同一のインスタンスではないがすべての文字が同じである場合

そして2.のケースでは、以下の3つの基準をクリアすれば同じ文字列であるということが言えます。
- 比較対象も文字列である
- 比較対象文字と文字数が同じである(Helloの場合は5)
- 先頭文字から一文字づつ比較していって最後まで同じ文字である
※①②がfalseであれば早急に同じ文字列でないとして時間短縮ができる
それをJavaのコードで表現しているのが以下のStringクラスのequalsメソッドです。
なお、valueは、インスタンスの文字列(今回の例では"Hello")が一文字ずつ格納されているchar型の配列です。
合計4つのif文があり少々複雑ですが、上記の説明と対応させてコメントを入れました。
public boolean equals(Object anObject) { if (this == anObject) { //1 return true; } if (anObject instanceof String) { // 2-① String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) {// 2-② char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) // 2-③ return false; i++; } return true; } } return false; }
(n- -!= 0)という表現が見慣れないかと思います。
これは、以下と同じなのですが、上記の書き方で1行短くしているのです。
while (n != 0) { n--; if (v1[i] != v2[i]) // 2-③ return false; i++; }
Stringクラスのequalsメソッドは2つの変数が同じインスタンスを参照していなくても、同じ値(文字列)を持っていれば同じ、という意味だということを私達は学びました。
それは、このようなコードで実現されているわけです。
中古車でしたら全く同じ車体でないと同じ車と判断してはいけませんね。
しかし、新車でしたら同じ車と判断しても良い車がたくさんありますね。
では、Javaプログラムでは何をもって同じインスタンスと判断しましょうか?
それは、プログラムの目的によって変わりますね。
したがってクラスごとにequalメソッドの内容は書き換えることができるのです。
Stringクラスのequalメソッドの例で見たように、サブクラスでスーパークラスのメソッドを変更することができます。
これをオーバーライド(override)と言います。
英語ではover「上に」ride「載せる」ということで優先させるという意味です。
オーバーロード(多重定義:引数の数や型が異なり名前が同じメソッドを宣言すること)と名前が似ているので注意してください。
また、スーパークラスのオーバーライドされたメソッドが上書き(overwrite)されてしまったわけではないので気をつけてください。
したがってスーパークラスのメソッドをサブクラスから呼び出すことも可能です。
その際は、superというキーワードを使いますがここではその詳細には触れません。
5.メソッドのオーバーライド(自作クラスの場合)
ここでオーバーライドが成立するための3つの条件を挙げます。
- メソッドのシグネチャーが、スーパークラスと同じである
- メソッドのアクセス制御がスーパークラスと同じか緩い
- 戻り値の型が、スーパークラスのメソッドと同じかサブクラス型である
ひとまず3、はこの後学ぶポリモーフィズムと関連することなので、今はおいておきます。
また、2、でアクセス制御を不用意に緩めるとセキュリティ問題が発生しうるのですが、本研修の範囲を超えますので割愛します。
興味のある方はご自身で上記リンクをたどってください。
「1.メソッドのシグネチャが、スーパークラスと同じである」については失敗することがあります。
スペルミスによりオーバーライドのつもりがそうなっていないことが起こり得るのですね。
そのために
@Override
というアノテーション(annotation)があります。
アノテーションとは注釈という意味でした。
アノテーションを挿入すると「オーバーライドのつもりがスペルミスによりオーバーライドになっていなかった」という間違いを防ぐ効果があります。
VehicleクラスのサブクラスとしてRacingCarクラスを作成し、runメソッドをオーバーライドしてみます。
<結果を表示する>
I'm driving fast.
オーバーライドしたサブクラスのメソッドが実行されました。
では、このことがどのように役立つのでしょうか?
それは、乗り物(Vehicle)の変数ですべての種類の乗り物(車、飛行機、船)が扱えるということです。
次のようなプログラムが書けるということになります。
※本来は一つのクラスに一つのJavaファイルを用意すべきですが、読みにくくなるため上記のサンプルコードは、簡易的に一つのファイルに複数のクラスを書いています。その際には、publicで宣言できるのは一つのクラスだけになります。
<結果を表示する>
I'm driving.
I'm flying.
I'm sailing.
I'm driving.
I'm flying.
<イメージ図>

今回のように「v.run()」という同じ命令にもかかわらず、インスタンスごとに違った処理を実行しました。
このようなオブジェクト指向の仕組みをポリモーフィズム(多態性)といいます。
6.オリジナルなequalsメソッドの実装
次に、オリジナルなequalsメソッドの実装を試してみましょう。
自転車クラスを考えます。
この自転車クラスは防犯登録番号(registryNumber)が同じなら、同一であると判断するとします。
先ほどのStringクラスのequalsメソッドを参考にしてみました。
他の乗り物クラスでもequalsメソッドを持つことを想定し、汎用性を考えて仮引数を(Vehicle aVehicle)としてみました。
ちなみにequalsメソッドもIDEの機能を使えば簡単に挿入できるうえスペルミスを減らすことになります。
<結果を表示する>
true
false
<イメージ図>

ここで一見無関係に見えるhashCodeをオーバーライドしていることを不思議に思う人もいるかもしれません。
実は、equalsメソッドをオーバーライドしたときにはhashCodeメソッドもオーバーライドしないと、コレクションフレームワークのHashMap/HashSetなどのハッシュ表の要素としたときに深刻なバグを生む可能性があるのです。
ちなみにこのハッシュ値はごちゃまぜにするという意味でハッシュドビーフ(hashed beef)などと同じ語源でしたね。
ここでのハッシュ値は、いわゆる情報セキュリティなどで出てくるハッシュ値のように、一方向性や不可逆性といったことまでは要求されません。
ここでは単純に防犯登録番号を返す仕様としています。
実は数多くのインスタンスから目的のインスタンスをequalsメソッドで探すときには2段階の探索が行われるのです。
つまり、最初はhashCodeの一致を見ます。
しかし、前述の通りhashCodeが偶然同じで別のインスタンスということはありえます。
そこで絞り込んだ対象に対してequalsメソッドで等しいかどうかを見るのです。
なお、この例
equals(Vehicle aVehicle)
のようにメソッドの仮引数をスーパークラス型にすることで、サブクラス型の実引数を受け取れるようにしておくことをポリモーフィズムを使ったメソッドの引数設計と呼ぶことがあります。
大切な考え方ですので押さえてください。
たとえ話をしますと、「お母さんアレ取って」と言って理解しあえる夫婦のようなものです。
アレの中身は状況により変わってもいいわけですね。
この会話例のように私達は日常生活で抽象化の恩恵を受けていますが、ポリモーフィズムは抽象化だとお考えください。
ちなみに、このあと学ぶインタフェースを使うと更に抽象化の恩恵が受けられます。
7.ポリモーフィズムで保守性が高まる
ここで、ポリモーフィズムを使わずにコーディングしたとします。
何が不都合なのでしょうか?
ポリモーフィズムを使わない場合は、クラスの種類に応じて呼び出すインスタンスメソッドが変わります。
そのためif文などで処理を分岐させる必要があります。
クラスの種類に応じてというところは、instanceof演算子を使えばいいです。
こんな風になりますね。
<ポリモーフィズムを使わない場合>
<結果を表示する>
I'm driving.
I'm flying.
想定していないクラスです
<イメージ図>

しかし、ここで新たにSurfingクラスを作るとします。
すると、“想定していないクラス”なのでmainメソッドのif文を追加する必要が出てきてしまうのです。
しかし、以下のポリモーフィズムを使ったコードは乗り物の種類が増えてもmainメソッドの中身を(ほとんど)書き換えなくて済みます。
<ポリモーフィズムを使った場合> ※ PatrolCar, Locket, Shipの3クラスにもextends Vehicleを追記したが再掲は省略。
<結果を表示する>
I'm driving.
I'm flying.
I'm sailing.
I'm surfing.
<イメージ図>

今動いているプログラムをいじるというのはとても勇気のいることです。
もっと複雑なコードで、しかも、mainメソッドの処理が現役で使われている処理だとしましょう。
そうするとなかなか変更するのは大変です。
ポリモーフィズムの活用の一面として、新しくクラスを追加しても変更は最低限で済むということがあります。
もし、あなたのプログラムがinstanceof演算子を使ってクラスの種類を判定していて、今後も判定すべきクラスが増えそうであれば、そこに不吉なにおいを嗅ぎ取ってくださいね。
8.toStringメソッドで出力内容をコントロールする
toStringメソッドをオーバーライドすることでSystem.out.println()メソッドに渡したオブジェクトの表示内容を分かりやすくできます。
ここで初心に戻って以下のプログラムを実行してみてください。
<結果を表示する>
chap10.Bicycle@a
@の前がクラス名、後ろがハッシュコードと呼ばれる16進数でした。
このとき、例えば、printlnメソッドを使い防犯登録番号を表示させることはできるのでしょうか?
上記の表示を以下のように変えたいとします。
私の防犯登録番号は、1234
toStringメソッドをオーバーライドすればできます。
では、IDEでprintlnメソッドの定義をさかのぼりましょう。
PrintStreamクラスの820行目以降です。
public void println(Object x) { String s = String.valueOf(x); synchronized (this) { print(s); newLine(); } }
2行目でStringクラスのvalueOfというstaticメソッドを使っています。これは、xが参照する様々なオブジェクトを、String型に変換するメソッドです。
※ちなみに、printメソッドとnewLineメソッドも見えていますね。synchronized (this) という記述は排他制御のためです。このsynchronizedブロックで囲まれた2行は、1インスタンスごとに必ず1つのスレッドからしか実行されないため、同時アクセスによって矛盾した表示がされないことが保証されています。
では、IDEでvalueOfメソッドの定義をさかのぼりましょう。飛び先のクラスはStringで2993行目以降です。
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
obj == nullでnull判定をして、nullであれば文字列"null"を、そうでなければ、toStringメソッドを呼んでいます。
では、さらにIDEでtoStringメソッドの定義をさかのぼりましょう。
クラスはObjectクラスです。
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
ここでやっとご本尊(?)らしき記述と対面することができました。
では、このtoStringメソッドをオーバーライドしたらどうなるでしょうか?
オリジナルな文字表現で標準出力できそうですね。
やってみましょう!
<結果を表示する>
私の防犯登録番号は、10
なお、オリジナルなクラスを作成したらtoStringメソッドをオーバーライドするというのはJavaプログラマの半ば常識となっています。
そうすることでデバッグ時に表示が分かりやすくなるからです。
また、そのようなtoStringメソッドですから、IDEで簡単に挿入できるようになっていますので方法は講師から説明いたします。
ただし、toStringメソッドで取得したデータを使いまわすようなプログラムは慎むべきです。
フィールドの値を取得する用途には、次回お話しするアクセサメソッドを使います。
ちなみに、ここまで来たらObjectクラスのhashCodeメソッドの中身も気になっている人もいるかもしれませんね。
試しにJavaのソースコードで定義を確認するとObjectクラスの100行目で、
public native int hashCode();
と書かれています。
中身のないメソッドです。
そしてnative修飾子がついています。
英語でも母国語を話せる人をnativeといいますね。
コンピュータの母国語はマシン語です。
C言語で書かれてマシン語にコンパイルされたコードです。
9.スーパークラスの土台の上にサブクラスは作られる
もう少し継承の仕組みのお話にお付き合いください。
以下のサンプルコードを実行したらどうなるでしょうか?
<結果を表示する>
Hello from SuperClass
これは、コンストラクタが継承されたわけではありません。
そうではなく、見えないだけで本当は以下のようなコードなのです。
見えていないコードを書き入れるとこうなっています。
<結果は同じ>
Subクラスのデフォルトコンストラクタの先頭行でスーパークラスのデフォルトコンストラクタを呼んでいたのです。
したがって3代に渡る継承関係を作るとこうなります。
<結果を表示する>
Hello from First
Hello from Second
Hello from Third
この順番をよくご記憶ください。
スーパークラスから順番にサブクラスがコンストラクトされていってます。
コンストラクターチェーンと呼ばれることもあります。
スーパークラスの土台の上にサブクラスは作られるのです。(一番下の土台はObjectクラス)
この図10.8が理解できれば、スーパークラスの参照でサブクラスが扱えるということも理解しやすいのではないでしょうか?

10.継承よりも委譲を選ぶ
先にも述べたとおり、あるクラスのある機能を取り込みたいばかりに、継承を使うというのは悪い設計です。
継承を使うとスーパークラスの変更がサブクラスに及ぶため、クラス間に依存関係が生じてしまうのです。
なお、依存関係が強いことを密結合、依存関係が弱いことを疎結合と情報処理の世界では呼ぶことがあります。
継承を使わなくても他のクラスの機能を取り込むことができます。
その方法を「依存」や「委譲」といいます。
次のサンプルプログラムを見てください。
<結果の例を表示する> ※キーボードからHelloと入力した場合
入力してください > Hello
Helloが入力されました
ご覧いただきたいのはDependencePc クラスのローカル変数にScannerクラスのインスタンスを割り当てている部分です。
これが依存です。
useの関係ともいいます。
クラス図で書くと下図10.9のようになります。
野球で例えるならピンチヒッターとして、一時的に起用された選手のような役割です。
このinputメソッドの中だけで活躍します。
クラス図ではこうなります。

もしも、ScannerクラスがPCクラスの部品と考えられて、複数のメソッドにまたがって使いたいクラスであれば、フィールドにインスタンスメンバとして宣言するほうが良いでしょう。
野球の例えでいえばスタメンのようなものです。
以下のサンプルプログラムをご覧ください。
<結果は上記と同じ>
ご注目いただきたいのは、フィールドにScannerクラスのインスタンス変数を宣言している部分です。
そうしておいて、inputメソッドでScannerクラスのnextLineメソッドを呼び出しています。
このようなクラスの関係を”has-a”の関係といいます。

クラス図では上図のようになります。
白抜きの◇があるクラスが矢印のクラスを持っているという意味です。
継承のところで見たようにクラス図の矢印はそれがどのような形のものであっても“依存”関係を表現するのでした。
(ここでの依存はクラス図の依存とは別の一般的な意味で使用しています。)
PcクラスはScannerクラスを持っているということは、PcクラスはScannerクラスがないと困る、すなわち依存しているというわけですね。
両方が自作のクラスであれば、矢印の先にあるクラスから作り始めたほうが簡単です。
このときnextLineメソッドを使いたいがためだけにPcクラスがScannerクラスを継承するのは間違いです。
※そもそもScannerクラスはfinalで宣言されているため継承できませんが
樹形図になっているクラスの体系をぐちゃぐちゃにしてしまいます。
"is-a"関係には後ほどお話する多重継承の問題や、サブクラスにスーパークラスの不要なフィールドやメソッドまでをも組み込んでしまうという問題があります。
”has-a”関係にはそのような問題はありません。
継承か委譲か迷ったら委譲を使う方が良いのです。
11.クラスの責務を考えてクラス設計をする
継承よりも移譲を選ぶという話題が出たついでにクラスの責務という考え方をご紹介します。
クラスの責務とは、このデータはどのオブジェクトが知っていて、どのオブジェクトが処理をすべきか、ということです。
具体例で説明します。
BicycleクラスとExample07のサンプルプログラムでは、registryNumberを知っているのも、equalsで同じかどうかを比較するのも、Bicycleクラスの責務と考えたということになります。
しかし、強引にequalsメソッドをExample07に持たせたとしたら以下のようなコードになります。
<結果を表示する>
true
false
このとき、もしも、Bicycleクラスを利用しているクラスが、Example1~100まで100個あったとしたらどうでしょうか?
その全てで、このequalsメソッドを書かなければいけません。
また、このequalsメソッドに変更が生じたとしたら、100箇所で修正しなければいけないことになります。
しかし、equalsメソッドを正しくBicycleクラスの責務と考えられたなら1箇所の修正で済むわけです。
12.ClassCastException
ここでは、クラスのキャストについて見ていきます。
「サブクラスは一種のスーパークラス」でした。
しかし、逆に、「スーパークラスは一種のサブクラスである」とは言えません。
ですからスーパークラスのインスタンスをサブクラスの参照に代入することはできません。
以下のサンプルプログラムを見てください。
<結果を表示する>
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
このとき送出されるのがClassCastExceptionという例外です。
「オブジェクトは一種の文字列である」とは言えませんね。
そのためこのようなキャストはできないという例外が発生したのでした。
この例外も新人エンジニア研修ではよく見かけますのでご記憶ください。
13.多重継承の禁止
なお、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では多重継承が禁じられているのです。
ただし、後で学ぶインタフェースというものを使うと多重継承“的”な仕組みが実現可能になりますのでお楽しみに。
今回は継承(拡張)について見てきました。
スーパークラスの変数で扱えることやポリモーフィズムなど便利そうな仕組みがありましたね。
ただし、「継承は最後の手段」という言葉があります。
Javaは単一継承のため、継承を使ったクラス設計はめったに採用されないと思ってください。
特に新入社員の皆さんの最初のお仕事ならばなおさらです。
ただし、継承関係にあるクラスを利用することは多くありますからその仕組みを理解しておくことはとても重要です。
そもそも全てのクラスはObjectクラスの子孫なのですから。
なお、継承されないものとして以下の3つがあることは知っておくとなおよいでしょう。
- プライベート宣言されたメンバ
- コンストラクタ
- スタティックメンバ(インスタンスの中に含まれないので)
今回は継承(拡張)について見てきました。
次回のテーマはカプセル化です。
これも、オブジェクト指向特有のテーマです。
さらに、継承よりも身近に使われるテクニックです。
カプセル化を使ってより頑強なクラスを作っていきましょう。
<まとめ:隣の人に正しく説明できたらチェックを付けましょう>
□すでにあるクラスのフィールドやメソッドを新しいクラスが引き継ぐことを継承というが、その際にサブクラスは一種のスーパークラスであるといえることが重要である
□オーバーライドとは、サブクラスでスーパークラスのメソッドを優先させることでポリモーフィズムが実現できる。ポリモーフィズムとは、同じメソッドに対して違った処理をさせることである
□メソッドの仮引数をスーパークラス型にすることで、サブクラス型の実引数を受け取れるようにしておくことをポリモーフィズムを使ったメソッドの引数設計と呼ぶ
□継承(is-a)か委譲(has-a)か迷ったら委譲を使う方が良い出す処理のことであり、クラス名と同じ名前で戻り値はない
□クラスの責務を考えてクラス設計をする
まとめができたら、アウトプットとして演習問題にチャレンジしましょう。
【今回の復習Youtube】
JavaSE8の解説に戻る
最後までお読みいただきありがとうございました。