Java道|11章のまとめ

継承を知ると、クラスの関係がつながり、役割が広がり、コードはもっと強くなる。
11章は、Javaオブジェクト指向の「親から子へ受け継ぐ力」を学ぶ章です。

11章では、Javaのオブジェクト指向の中でも、とても重要な 継承 を中心に学びました。

継承は、すでに作ってあるクラスを土台にして、新しいクラスを効率よく作るためのしくみです。
まったくゼロから作り直すのではなく、親クラスの力を受け継ぎながら、必要な特徴だけを子クラスに追加できます。

鬼滅の刃風にたとえると、まず DemonSlayer クラス という鬼殺隊士の共通の型があります。
そこから、より特別な役割を持つ PillarSlayer クラス や、水柱を表す WaterPillarSlayer クラス を作っていくようなイメージです。

親クラスに共通部分をまとめ、子クラスでは個性を加える。
この考え方が、11章全体を通して大切な流れでした。

11章で扱った内容は、単に extends の書き方だけではありません。

学んだ内容役割
継承親クラスの機能を子クラスへ受け継ぐ
スーパークラスとサブクラス親と子の関係を整理する
protected子クラスから使えるメンバを作る
オーバーライド親のメソッドを子クラス用に作り直す
ポリモーフィズム同じ呼び出しで実体ごとに違う動きをさせる
オーバーロードとの違い同名メソッドの使い分けを整理する
final変更・継承・上書きを制限する
Object クラスすべてのクラスの共通の親
toString()オブジェクトを文字列で表す
equals()オブジェクトが同じかどうか調べる
getClass()オブジェクトの実体のクラス情報を調べる

つまり11章は、既存のクラスをどう活かし、どう広げ、どこを守り、どう共通化するかを学ぶ章です。
ここが見えてくると、Javaのオブジェクト指向は単なる文法ではなく、クラス設計そのものに関わる考え方だと分かってきます。

継承は親の力を受け継いで新しいクラスを作るしくみ

11章の中心にあるのは、継承です。

継承では、スーパークラスをもとにしてサブクラスを作ります。
サブクラスは、スーパークラスのフィールドやメソッドを受け継ぎながら、自分に必要な新しい機能を追加できます。

鬼滅の刃風にたとえると、DemonSlayer クラスには鬼殺隊士としての共通情報があります。

DemonSlayer クラスの共通要素内容
name隊士の名前
rank階級
show()隊士情報を表示する
setSlayer()名前や階級を設定する

そこから PillarSlayer クラスを作る場合、DemonSlayer の共通要素を受け継ぎます。
そのうえで、柱だけが持つ担当区域や特別な表示機能を追加できます。

PillarSlayer クラスで追加する要素内容
area担当区域
setArea()担当区域を設定する
show() の作り直し柱らしい情報表示にする

このように、親クラスには共通部分を書き、子クラスには個別の特徴を書きます。

同じようなコードを何度も書かなくて済むので、継承はコードの再利用に役立ちます。

サブクラスはスーパークラスのメンバを受け継ぐ

継承を使うと、サブクラスはスーパークラスのメンバを受け継げます。

たとえば、DemonSlayer クラスに show() がある場合、PillarSlayer クラスはその show() を使えます。
PillarSlayer 側に同じ処理をもう一度書かなくてもよいのが大きなポイントです。

鬼滅の刃風にたとえると、柱は柱としての特別な役割を持っています。
しかし、柱である前に鬼殺隊士でもあります。

そのため、鬼殺隊士としての名前や階級、基本表示の機能を受け継げます。

考え方鬼滅の刃風のイメージ
スーパークラス鬼殺隊士の共通設計
サブクラス柱などの特別な隊士
継承鬼殺隊士としての基本を受け継ぐ
追加柱としての特徴を加える

このように、継承は「共通部分を親にまとめ、違いだけを子に書く」ためのしくみです。

protected は親子関係の中で使いやすいアクセス指定

11章では、メンバへのアクセス範囲も学びました。

スーパークラスの private メンバには、サブクラスから直接アクセスできません。
一方で、protected メンバなら、サブクラスから直接アクセスできます。

アクセス修飾子サブクラスから直接アクセスできるか役割
privateできないそのクラス内部だけで守る
protectedできる子クラスにも使わせる
publicできる外部にも公開する

鬼滅の刃風にたとえると、DemonSlayer クラスが持つ情報の中には、親クラスだけで管理したいものもあります。
一方で、PillarSlayer クラスにも使わせたい情報もあります。

たとえば、name や rank を PillarSlayer の show() で表示したいなら、protected にしておくと扱いやすくなります。

情報private が向く場合protected が向く場合
name親クラスだけで厳密に管理したい子クラスでも表示したい
rank外部や子から直接触らせたくない子クラスの処理でも使いたい
areaPillarSlayer 内だけで使う親子で共有する必要がない

protected は、カプセル化を完全に崩すものではありません。
親子関係の中で必要な情報を共有しやすくするための指定です。

オーバーライドは親のメソッドを子クラスらしく作り直すしくみ

オーバーライドも、11章の大きなテーマでした。

オーバーライドとは、スーパークラスと同じメソッド名・同じ引数の形式を持つメソッドを、サブクラス側で定義しなおすことです。

たとえば、DemonSlayer クラスに show() があるとします。

public void show()
{
    System.out.println("隊士の名前を表示します。");
    System.out.println("階級を表示します。");
}

PillarSlayer クラスでは、担当区域も表示したいので、同じ show() を作り直します。

public void show()
{
    System.out.println("柱の名前を表示します。");
    System.out.println("階級を表示します。");
    System.out.println("担当区域を表示します。");
}

このように、同じ show() でも、PillarSlayer のオブジェクトで呼び出したときは PillarSlayer 側の show() が動きます。

鬼滅の刃風にたとえると、一般隊士には一般隊士の自己紹介があります。
柱には柱としての自己紹介があります。

クラスshow() の内容
DemonSlayer名前と階級を表示
PillarSlayer名前、階級、担当区域を表示

オーバーライドは、継承が単なる受け継ぎで終わらず、子クラスらしい振る舞いを表現できることを示しています。

スーパークラス型でまとめて扱えることが多態性につながる

11章では、サブクラスのオブジェクトをスーパークラス型の変数で扱えることも学びました。

これは、サブクラスのオブジェクトがスーパークラスの一種でもあるからです。

たとえば、PillarSlayer は DemonSlayer を継承しています。
そのため、次のように書けます。

DemonSlayer slayer1 = new PillarSlayer();

変数の型は DemonSlayer です。
しかし、実際に入っているオブジェクトは PillarSlayer です。

ここで show() を呼び出すと、実体である PillarSlayer 側の show() が動きます。

変数の型実体show() で動くもの
DemonSlayerDemonSlayerDemonSlayer の show()
DemonSlayerPillarSlayerPillarSlayer の show()

これがポリモーフィズム、多態性です。

鬼滅の刃風にたとえると、司令部は全員を「鬼殺隊士」としてまとめて扱えます。
しかし、実際に show() を命じると、一般隊士は一般隊士らしく、柱は柱らしく自己紹介します。

同じ命令でも、実体によって動きが変わる。
これが多態性の大切な感覚です。

オーバーライドとオーバーロードは似ているが別物

11章では、オーバーライドとオーバーロードの違いも整理しました。

名前は似ていますが、意味はかなり違います。

用語意味鬼滅の刃風のイメージ
オーバーロード同じメソッド名で、引数の形式が異なるメソッドを複数定義する同じ技名で使い方違いを用意する
オーバーライド親クラスと同じメソッド名・同じ引数の形式のメソッドを子クラスで定義する親の技を子が自分流に作り直す

見分けるときは、引数の形を見ると分かりやすいです。

判断ポイント結論
同じ名前で引数が違うオーバーロード
同じ名前で引数も同じ、継承関係があるオーバーライド

鬼滅の刃風にたとえると、useBreathing()、useBreathing(int form)、useBreathing(int form, String target) のように、同じ技名で条件違いを用意するのがオーバーロードです。

一方、DemonSlayer の show() を PillarSlayer で柱用に作り直すのがオーバーライドです。

final はそれ以上変更させないための修飾子

11章では、継承やオーバーライドが便利である一方で、すべてを自由に変えさせればよいわけではないことも学びました。

そこで登場したのが final です。

final は、付ける場所によって意味が変わります。

final を付ける場所意味
メソッドオーバーライドできない
クラス継承できない
フィールド値を変更できない

鬼滅の刃風にたとえると、final は「ここは変えてはいけない」という封印のようなものです。

final の対象鬼滅の刃風のイメージ
final メソッドこの技の型は変えるな
final クラスこの隊士の型は完成形なので派生させるな
final フィールドこの数値は隊の決まりだから変えるな

継承はクラスを自由に広げるためのしくみです。
しかし、設計では、自由に広げる部分と、固定して守る部分を分けることも大切です。

final は、その境界線をコードに表すための修飾子です。

Java のクラスは最終的に Object クラスにつながる

11章の後半では、Object クラスも学びました。

Javaでは、スーパークラスを指定しないクラスは、自動的に Object クラスのサブクラスになります。

たとえば、次のように書いた場合です。

class DemonSlayer
{
}

extends を書いていなくても、考え方としては次のようになります。

class DemonSlayer extends Object
{
}

つまり、Javaのすべてのクラスは最終的に Object クラスにつながります。

鬼滅の刃風にたとえると、鬼殺隊士、柱、日輪刀、任務書など、いろいろなクラスがあっても、Javaの世界ではすべて「オブジェクト」としての共通土台を持っています。

Object クラスから受け継ぐ代表的なメソッドには、次のものがあります。

メソッド役割
toString()オブジェクトを表す文字列を返す
equals()オブジェクトが同じかどうかを調べる
getClass()オブジェクトのクラス情報を返す

Object クラスを理解すると、Javaのクラスがバラバラに存在しているのではなく、共通の親につながっていることが分かります。

toString() はオブジェクトの名乗り方を決める

toString() は、オブジェクトを文字列で表すメソッドです。

オブジェクトを System.out.println() に渡したとき、この toString() の戻り値が表示されます。

System.out.println(slayer1);

このように書いたとき、内部では slayer1.toString() が呼び出され、その戻り値が表示されます。

Object クラス由来の toString() をそのまま使うと、DemonSlayer@1a2b3c のような機械的な表示になりがちです。

そこで、自分のクラスで toString() をオーバーライドすると、分かりやすい文字列を返せるようになります。

状態表示例
toString() をオーバーライドしないDemonSlayer@1a2b3c
toString() をオーバーライドする名前:水月 階級:水柱

鬼滅の刃風にたとえると、toString() は隊士の自己紹介です。
自分の名前や階級を分かりやすく名乗らせるために、toString() を作り直します。

equals() は同じオブジェクトかを調べる基本メソッド

equals() は、2つの変数が同じオブジェクトを指しているかどうかを調べるメソッドです。

Object クラス由来の equals() では、基本的に同じ実体を指している場合に true、別のオブジェクトなら false を返します。

鬼滅の刃風にたとえると、2枚の隊士札があるとします。
その2枚が同じ隊士本人を指しているなら true です。
似ていても別々に作られた隊士を指しているなら false です。

比較結果
同じオブジェクトを指しているtrue
別々のオブジェクトを指しているfalse

また、String クラスでは equals() がオーバーライドされていて、文字列の内容が同じかどうかを見るように作り直されています。

クラスequals() の考え方
Object 由来同じ実体かどうか
String文字列の内容が同じかどうか

このように、equals() はクラスごとに「同じ」の意味を作り直せるメソッドでもあります。

getClass() はオブジェクトの正体を調べる

getClass() は、オブジェクトが属しているクラスの情報を返すメソッドです。
戻り値は Class クラスのオブジェクトです。

スーパークラス型の配列で複数のオブジェクトをまとめて扱っているときでも、getClass() を使えば、そのオブジェクトが実際にはどのクラスなのかを確認できます。

たとえば、次のような配列を考えます。

DemonSlayer[] slayers = new DemonSlayer[2];

slayers[0] = new DemonSlayer();
slayers[1] = new PillarSlayer();

配列の型は DemonSlayer[] です。
しかし、中に入っている実体は異なります。

配列要素実体getClass() の結果
slayers[0]DemonSlayerclass DemonSlayer
slayers[1]PillarSlayerclass PillarSlayer

鬼滅の刃風にたとえると、名簿では全員が鬼殺隊士として並んでいても、実際には一般隊士や柱が混ざっています。
getClass() を使うと、その正体を確認できます。

図:11章全体の継承の流れ

↓クリックすると拡大表示されます。

この図が示していること

この図では、11章で学んだ継承の全体像を表しています。

一番上には Object クラスがあります。
その下に DemonSlayer、PillarSlayer、WaterPillarSlayer が続きます。

この階層から、Javaのクラスは最終的に Object クラスにつながり、そこから共通の基本機能を受け継ぐことが分かります。

また、DemonSlayer には共通部分を置き、PillarSlayer では担当区域やオーバーライドした show() を追加しています。

右側の protected は、親子関係の中で子クラスにも使わせたいメンバを表します。
左側の final は、変更させたくない部分を固定するための仕組みを表します。

図:ポリモーフィズムと Object の基本メソッド

↓クリックすると拡大表示されます。

この図が示していること

この図では、11章後半で学んだポリモーフィズムと Object クラスの基本メソッドを整理しています。

左側では、DemonSlayer[] 配列に DemonSlayer オブジェクトと PillarSlayer オブジェクトが一緒に入っています。
同じ slayers[i].show() という呼び出しでも、実体が DemonSlayer なら DemonSlayer の show()、実体が PillarSlayer なら PillarSlayer の show() が動きます。

右側では、Object クラス由来の基本メソッドをまとめています。

メソッド役割
toString()オブジェクトの名乗り方を決める
equals()同じ実体かどうかを確認する
getClass()実体のクラス情報を確認する

この図から分かることは、スーパークラス型でまとめて扱っても、実体ごとの個性は消えないということです。
さらに、どのクラスも Object クラス由来の基本機能を持っていることも分かります。

11章でいちばん大事な感覚

11章で学んだことをひとつの感覚にまとめるなら、
親クラスを土台にして、新しいクラスを作り、その関係の中で役割を分けていく考え方
です。

鬼滅の刃風にたとえると、共通の鬼殺隊士の型があり、そこから柱や水柱のような個性ある型が生まれます。
しかし、それぞれが完全にバラバラなのではありません。

共通部分は親にあります。
個性は子にあります。
同じ命令でも、実体によって動きが変わります。
そして、すべてのクラスは Object という共通の土台につながっています。

11章の考え方内容
共通部分は親にまとめるDemonSlayer に name、rank、show() などを置く
個性は子に追加するPillarSlayer に area などを追加する
必要なら作り直すshow() をオーバーライドする
まとめて扱うDemonSlayer 型で PillarSlayer を扱う
実体ごとに動きを変えるポリモーフィズム
守る部分は固定するfinal を使う
すべてのクラスは Object につながるtoString()、equals()、getClass() を受け継ぐ

この感覚がつかめると、継承は単なる文法ではなく、クラス設計そのものを考えるための強力な考え方だと分かります。