
Java道|スーパークラス型とポリモーフィズムの理解
同じ命令でも、動く技は隊士ごとに変わる。
ポリモーフィズムを理解すると、親クラス型でまとめても、子クラスの個性がきちんと生きる理由が見えてきます。
継承を学ぶと、サブクラスはスーパークラスの性質を受け継ぎながら、自分だけの特徴を追加できることが分かります。
さらに、オーバーライドを使うと、スーパークラスにあるメソッドをサブクラス側で自分用に作り直せます。
ここまで理解できると、次に大切になるのが スーパークラス型でサブクラスのオブジェクトを扱う という考え方です。
Javaでは、サブクラスのオブジェクトをサブクラス型の変数で扱うだけでなく、スーパークラス型の変数でも扱えます。
鬼滅の刃風にたとえると、PillarSlayer は柱として特別な隊士ですが、まず DemonSlayer の一種でもあります。
そのため、DemonSlayer 型の変数で PillarSlayer オブジェクトを指すことができます。
DemonSlayer slayer1;
slayer1 = new PillarSlayer();このとき、変数の型は DemonSlayer ですが、実際に入っているオブジェクトは PillarSlayer です。
ここでおもしろいのが、show() のようにオーバーライドされたメソッドを呼び出したときの動きです。
変数の型がスーパークラスでも、実体がサブクラスなら、実体に合ったサブクラス側の show() が動きます。
これが ポリモーフィズム、つまり多態性の大切な考え方です。
サブクラスのオブジェクトはスーパークラス型でも扱える
まず大切なのは、サブクラスのオブジェクトをスーパークラス型の変数に入れられるという点です。
たとえば、DemonSlayer というスーパークラスと、それを継承した PillarSlayer というサブクラスがあるとします。
このとき、次の2つの書き方はどちらもできます。
| 書き方 | 意味 |
|---|---|
| PillarSlayer slayer1 = new PillarSlayer(); | サブクラス型の変数で、サブクラスのオブジェクトを扱う |
| DemonSlayer slayer1 = new PillarSlayer(); | スーパークラス型の変数で、サブクラスのオブジェクトを扱う |
なぜこのような書き方ができるのでしょうか。
理由は、PillarSlayer が DemonSlayer の一種だからです。
鬼滅の刃風にたとえると、柱は特別な隊士です。
しかし、柱である前に、鬼殺隊士でもあります。
つまり、
| 関係 | 意味 |
|---|---|
| PillarSlayer は DemonSlayer を継承している | 柱は鬼殺隊士の一種 |
| PillarSlayer オブジェクトを DemonSlayer 型で扱える | 柱を鬼殺隊士としてまとめて扱える |
ということです。
この「子クラスは親クラスの一種である」という関係が、スーパークラス型で扱える理由です。
↓クリックすると拡大表示されます。

変数の型と実際のオブジェクトは分けて考える
ポリモーフィズムを理解するうえで、とても大切なのが、変数の型 と 実際のオブジェクトのクラス を分けて考えることです。
たとえば、次のコードを見てください。
DemonSlayer slayer1;
slayer1 = new PillarSlayer();この場合、整理すると次のようになります。
| 見るポイント | 内容 |
|---|---|
| 変数 slayer1 の型 | DemonSlayer |
| 実際に作られたオブジェクト | PillarSlayer |
見た目の型は DemonSlayer です。
しかし、中に入っている実体は PillarSlayer です。
鬼滅の刃風にたとえると、司令部の名簿では「鬼殺隊士」として扱っていても、実際に前に立っているのが「柱」なら、柱としての振る舞いを見せるということです。
ここがポリモーフィズムの出発点です。
スーパークラス型で扱ってもサブクラスの show() が呼ばれる
今回の中心はここです。
スーパークラス型の変数でサブクラスのオブジェクトを扱っていても、オーバーライドされたメソッドを呼び出すと、実際のオブジェクトのクラスに応じたメソッドが動きます。
たとえば、変数の型が DemonSlayer でも、実体が PillarSlayer なら、show() を呼び出したときには PillarSlayer の show() が呼ばれます。
| 変数の型 | 実際のオブジェクト | show() で動くもの |
|---|---|---|
| DemonSlayer | DemonSlayer | DemonSlayer の show() |
| DemonSlayer | PillarSlayer | PillarSlayer の show() |
これは、Javaがオーバーライドされたメソッドについて、実際のオブジェクトのクラスを見て呼び出すメソッドを決めるからです。
鬼滅の刃風にたとえると、司令部が「情報を見せて」と同じ命令を出しても、一般隊士なら一般隊士らしい表示、柱なら柱らしい表示になります。
同じ show() という命令でも、相手の正体によって動きが変わる。
これがポリモーフィズムです。
鬼滅の刃風に考える多態性
ポリモーフィズムは、日本語では多態性と呼ばれます。
多態性とは、簡単にいうと、
同じ呼び出し方でも、実際のオブジェクトによって動きが変わること
です。
鬼滅の刃風にたとえると、司令部が複数の隊士に対して、同じように「自己紹介して」と命じたとします。
すると、
| 実際の隊士 | show() の動き |
|---|---|
| 一般隊士 | 名前と階級を表示する |
| 柱 | 名前、階級、担当区域を表示する |
というように、同じ show() でも表示内容が変わります。
命令の形は同じです。
けれども、実際に動く内容は相手によって変わります。
これが、ポリモーフィズムの感覚です。
ポリモーフィズムの動作確認
ファイル名:Sample5.java
class DemonSlayer
{
protected String name;
protected String rank;
public DemonSlayer()
{
name = "隊士未登録";
rank = "階級未設定";
System.out.println("鬼殺隊士を作成しました。");
}
public void setSlayer(String n, String r)
{
name = n;
rank = r;
System.out.println("名前を" + name + "に、階級を" + rank + "にしました。");
}
public void show()
{
System.out.println("隊士の名前は" + name + "です。");
System.out.println("階級は" + rank + "です。");
}
}
class PillarSlayer extends DemonSlayer
{
private int area;
public PillarSlayer()
{
area = 0;
System.out.println("柱クラスの隊士を作成しました。");
}
public void setArea(int a)
{
area = a;
System.out.println("担当区域を" + area + "にしました。");
}
public void show()
{
System.out.println("柱の名前は" + name + "です。");
System.out.println("階級は" + rank + "です。");
System.out.println("担当区域は" + area + "です。");
}
}
class Sample5
{
public static void main(String[] args)
{
DemonSlayer slayer1;
slayer1 = new PillarSlayer();
slayer1.setSlayer("水月", "水柱");
slayer1.show();
}
}実行結果
鬼殺隊士を作成しました。
柱クラスの隊士を作成しました。
名前を水月に、階級を水柱にしました。
柱の名前は水月です。
階級は水柱です。
担当区域は0です。Sample5.java で注目するところ
このプログラムで特に見てほしいのは、次の部分です。
DemonSlayer slayer1;
slayer1 = new PillarSlayer();変数 slayer1 の型は DemonSlayer です。
しかし、実際に作っているオブジェクトは PillarSlayer です。
整理すると、次のようになります。
| 項目 | 内容 |
|---|---|
| 変数の型 | DemonSlayer |
| 実際のオブジェクト | PillarSlayer |
この状態で、次のように show() を呼び出しています。
slayer1.show();ここで動くのは DemonSlayer の show() ではありません。
実際に入っているオブジェクトが PillarSlayer なので、PillarSlayer の show() が呼ばれます。
つまり、変数の型は親でも、実体が子なら、オーバーライドされたメソッドは子クラス版が動くということです。
なぜ PillarSlayer の show() が呼ばれるのか
理由は、Javaがオーバーライドされたメソッドを呼び出すとき、実際のオブジェクトのクラスを見て判断するからです。
Sample5.java では、slayer1 の型は DemonSlayer です。
DemonSlayer slayer1;しかし、実体は PillarSlayer です。
slayer1 = new PillarSlayer();そのため、show() を呼び出すと、PillarSlayer の show() が実行されます。
| 呼び出し | 変数の型 | 実体 | 実行されるメソッド |
|---|---|---|---|
| slayer1.show() | DemonSlayer | PillarSlayer | PillarSlayer の show() |
鬼滅の刃風にたとえると、名簿上は「鬼殺隊士」として扱われています。
でも、実際に前に出てきたのが柱なら、柱としての情報を見せます。
これが、ポリモーフィズムの大切な動きです。
ただしサブクラス独自のメソッドはそのまま呼べない
ここはとても重要な注意点です。
スーパークラス型の変数でサブクラスのオブジェクトを扱うと、オーバーライドされた共通メソッドは実体に応じて動きます。
しかし、サブクラスで新しく追加した独自メソッドは、そのままでは呼び出せません。
Sample5.java では、setArea() は PillarSlayer 独自のメソッドです。
public void setArea(int a)しかし、変数 slayer1 の型は DemonSlayer です。
DemonSlayer slayer1;そのため、次のようには書けません。
slayer1.setArea(7);なぜなら、DemonSlayer 型として見ると、setArea() というメソッドは存在しないからです。
この違いを表にすると、次のようになります。
| 呼び出し | 使えるか | 理由 |
|---|---|---|
| slayer1.setSlayer("水月", "水柱") | 使える | DemonSlayer に定義されているから |
| slayer1.show() | 使える | DemonSlayer にあり、実体に応じてオーバーライドが働くから |
| slayer1.setArea(7) | 使えない | DemonSlayer 型には setArea() がないから |
ここで押さえておきたい考え方は、次の2つです。
| 見るポイント | 決まること |
|---|---|
| 変数の型 | 呼び出せるメソッドが決まる |
| 実際のオブジェクト | オーバーライドされたメソッドの中身が決まる |
つまり、何を呼び出せるかは変数の型で決まり、実際にどの処理が動くかはオブジェクトの実体で決まる と考えると整理しやすいです。
スーパークラス型で扱う意味
では、なぜわざわざスーパークラス型で扱うのでしょうか。
その理由は、いろいろなサブクラスのオブジェクトを、同じ親クラス型でまとめて扱えるからです。
たとえば、DemonSlayer を親クラスとして、次のようなクラスがあるとします。
| クラス | 意味 |
|---|---|
| DemonSlayer | 鬼殺隊士の基本クラス |
| PillarSlayer | 柱の隊士クラス |
| FlamePillarSlayer | 炎柱の隊士クラス |
| WaterPillarSlayer | 水柱の隊士クラス |
これらをすべて DemonSlayer 型として扱えれば、同じ配列や同じループで処理しやすくなります。
そして show() を共通メソッドとして用意しておけば、呼び出し側は同じように show() と書くだけで、それぞれのクラスに合った表示が行われます。
これが、ポリモーフィズムの便利なところです。
図:スーパークラス型でサブクラスを指すイメージ
↓クリックすると拡大表示されます。

この図が示していること
この図では、DemonSlayer 型の変数 slayer1 が、PillarSlayer オブジェクトを指している様子を表しています。
左側の変数カードは、見た目の型が DemonSlayer であることを示しています。
右側のオブジェクトカードは、実体が PillarSlayer であることを示しています。
ここで slayer1.show() を呼び出すと、変数の型ではなく、実体である PillarSlayer の show() が動きます。
この図から分かることは、次の2つです。
| 見るポイント | 意味 |
|---|---|
| 変数の型 | 呼び出せるメソッドの範囲を決める |
| 実体のクラス | オーバーライドされたメソッドの実行内容を決める |
オブジェクト配列でポリモーフィズムを確認する
ファイル名:Sample6.java
class DemonSlayer
{
protected String name;
protected String rank;
public DemonSlayer()
{
name = "隊士未登録";
rank = "階級未設定";
System.out.println("鬼殺隊士を作成しました。");
}
public void setSlayer(String n, String r)
{
name = n;
rank = r;
System.out.println("名前を" + name + "に、階級を" + rank + "にしました。");
}
public void show()
{
System.out.println("隊士の名前は" + name + "です。");
System.out.println("階級は" + rank + "です。");
}
}
class PillarSlayer extends DemonSlayer
{
private int area;
public PillarSlayer()
{
area = 0;
System.out.println("柱クラスの隊士を作成しました。");
}
public void setArea(int a)
{
area = a;
System.out.println("担当区域を" + area + "にしました。");
}
public void show()
{
System.out.println("柱の名前は" + name + "です。");
System.out.println("階級は" + rank + "です。");
System.out.println("担当区域は" + area + "です。");
}
}
class Sample6
{
public static void main(String[] args)
{
DemonSlayer[] slayers;
slayers = new DemonSlayer[2];
slayers[0] = new DemonSlayer();
slayers[0].setSlayer("蒼真", "隊士");
slayers[1] = new PillarSlayer();
slayers[1].setSlayer("水月", "水柱");
for(int i = 0; i < slayers.length; i++){
slayers[i].show();
}
}
}実行結果
鬼殺隊士を作成しました。
名前を蒼真に、階級を隊士にしました。
鬼殺隊士を作成しました。
柱クラスの隊士を作成しました。
名前を水月に、階級を水柱にしました。
隊士の名前は蒼真です。
階級は隊士です。
柱の名前は水月です。
階級は水柱です。
担当区域は0です。配列でまとめるとポリモーフィズムの良さがよく見える
Sample6.java では、DemonSlayer 型の配列を用意しています。
DemonSlayer[] slayers;
slayers = new DemonSlayer[2];この配列には、DemonSlayer オブジェクトと PillarSlayer オブジェクトの両方を入れています。
slayers[0] = new DemonSlayer();
slayers[1] = new PillarSlayer();これは、PillarSlayer が DemonSlayer の一種だからできることです。
配列の中身を整理すると、次のようになります。
| 配列要素 | 変数としての型 | 実際のオブジェクト |
|---|---|---|
| slayers[0] | DemonSlayer | DemonSlayer |
| slayers[1] | DemonSlayer | PillarSlayer |
そして、ループでは次のように show() を呼び出しています。
for(int i = 0; i < slayers.length; i++){
slayers[i].show();
}呼び出し方は、どちらも同じです。
slayers[i].show();
しかし、実際に動く show() は、配列の中に入っているオブジェクトによって変わります。
| 対象 | 実体 | 実行される show() |
|---|---|---|
| slayers[0] | DemonSlayer | DemonSlayer の show() |
| slayers[1] | PillarSlayer | PillarSlayer の show() |
これが、ポリモーフィズムの大きな魅力です。
同じ show() という呼び出しでも、相手に応じて適切な処理が自動で動きます。
もしポリモーフィズムがなかったら
もしポリモーフィズムがなければ、オブジェクトごとに種類を確認して、別々に処理を分ける必要があります。
たとえば、次のような考え方になってしまいます。
| ポリモーフィズムがない場合 | ポリモーフィズムがある場合 |
|---|---|
| これは一般隊士か、柱かを毎回確認する | show() を呼ぶだけでよい |
| クラスごとに処理を分ける | 実体に応じて自動で切り替わる |
| コードが長くなりやすい | コードが整理しやすい |
| 呼び出し側が細かい違いを知る必要がある | 違いは各クラスに任せられる |
ポリモーフィズムがあると、呼び出し側は「show() を呼べばよい」と考えるだけで済みます。
実際にどう表示するかは、各クラスの show() に任せられます。
鬼滅の刃風にたとえると、司令部は「各隊士、情報を示せ」と命じるだけです。
一般隊士は一般隊士として、柱は柱として、それぞれの情報を表示します。
図:配列でまとめても個性が生きるイメージ
↓クリックすると拡大表示されます。

この図が示していること
この図では、DemonSlayer[] 配列の中に、DemonSlayer オブジェクトと PillarSlayer オブジェクトが一緒に入っている様子を表しています。
配列の型は DemonSlayer[] です。
しかし、その中には DemonSlayer の実体だけでなく、DemonSlayer を継承した PillarSlayer の実体も入れられます。
そして、ループでは同じように show() を呼び出しています。
slayers[i].show();それでも、実際には、
| 実体 | 動くメソッド |
|---|---|
| DemonSlayer | DemonSlayer の show() |
| PillarSlayer | PillarSlayer の show() |
というように切り替わります。
この図から分かることは、まとめて扱っても、それぞれの個性は失われない ということです。
オーバーライドとポリモーフィズムのつながり
ポリモーフィズムは、オーバーライドと深くつながっています。
流れとしては、次のように考えると分かりやすいです。
| 手順 | 内容 |
|---|---|
| 1 | スーパークラスに共通メソッド show() を用意する |
| 2 | サブクラスで show() をオーバーライドする |
| 3 | スーパークラス型で複数のオブジェクトをまとめて扱う |
| 4 | 同じ show() 呼び出しで、実体に応じた処理が動く |
Sample6.java では、DemonSlayer と PillarSlayer の両方を DemonSlayer[] に入れています。
そして、同じ slayers[i].show() を呼び出しています。
それでも、実体が DemonSlayer なら DemonSlayer の show()、実体が PillarSlayer なら PillarSlayer の show() が動きます。
これが、オーバーライドとポリモーフィズムのつながりです。
多態性はまとめやすさと分かりやすさを生む
ポリモーフィズムの良さは、コードを短くできることだけではありません。
設計の見通しがよくなることも大きな利点です。
たとえば show() というメソッド名が共通なら、コードを読む人は「これは情報を表示する処理だ」とすぐに分かります。
そのうえで、具体的に何を表示するかは各クラスに任せられます。
| 呼び出し側 | 各クラス側 |
|---|---|
| show() を呼ぶだけでよい | 自分に合った show() を定義する |
| 細かい種類を知らなくてよい | 自分の表示内容に責任を持つ |
| 同じ配列やループで扱える | 実体ごとに違う処理を実行できる |
鬼滅の刃風にたとえると、司令部は全員を鬼殺隊士としてまとめて扱えます。
しかし、実際に show() を命じると、一般隊士は一般隊士として、柱は柱として、自分に合った情報を示します。
鬼滅の刃風にスーパークラス型とポリモーフィズムを整理する
DemonSlayer は、鬼殺隊士全体を表す共通の型です。
PillarSlayer は、その DemonSlayer を受け継いだ柱の型です。
そのため、PillarSlayer のオブジェクトは DemonSlayer 型として扱えます。
しかし、DemonSlayer 型で扱ったからといって、PillarSlayer らしさが消えるわけではありません。
show() を呼び出せば、実体が PillarSlayer なら PillarSlayer の show() が動きます。
| Javaの考え方 | 鬼滅の刃風のイメージ |
|---|---|
| スーパークラス型で扱う | 柱を鬼殺隊士として名簿に並べる |
| 実体はサブクラス | 実際には柱として存在している |
| show() を呼ぶ | 情報を示せと命じる |
| サブクラス版が動く | 柱らしい情報表示をする |
ひとことで言えば、ポリモーフィズムは、
同じ命令で呼び出しても、前に立っている隊士に合わせて技が変わる仕組み
です。
この内容で押さえておきたいポイント
| ポイント | 内容 |
|---|---|
| サブクラスのオブジェクト | スーパークラス型の変数で扱える |
| 理由 | サブクラスはスーパークラスの一種だから |
| 変数の型 | 呼び出せるメソッドの範囲を決める |
| 実際のオブジェクト | オーバーライドされたメソッドの実行内容を決める |
| show() の動き | 実体が PillarSlayer なら PillarSlayer の show() が動く |
| サブクラス独自メソッド | スーパークラス型の変数からはそのまま呼べない |
| 配列での利用 | DemonSlayer[] に DemonSlayer と PillarSlayer をまとめられる |
| ポリモーフィズム | 同じ呼び出しで、実体ごとに違う処理が動く |
スーパークラス型でまとめると、複数の種類のオブジェクトを同じ形で扱えるようになります。
そして、オーバーライドされたメソッドを呼び出せば、実際のオブジェクトに合った処理が動きます。
この「まとめて扱えるのに、個性は消えない」という感覚が、ポリモーフィズムの大切なポイントです。
