新人エンジニア研修で知っておきたい継承の使い方
なぜ、継承の理解が重要なのか、その理由。
この記事では、弊社の新人エンジニア研修の参考にJavaを解説します。
前回はインスタンスの活用について解説しました。今回は継承(拡張)について解説します。
継承を使うことで異なるクラスを一定のグループとして扱えるようになります。乗り物を例に取れば、タクシーやバス、客船など色々な乗り物があります。これらはそれぞれ違ったクラスと考えることもできますが、大きく乗り物という一つのカテゴリに分類できますね。そうすると同じ性質や動作をもっていると言えます。それらの乗り物を同じグループとして扱えるのが継承です。
継承はとても巧妙な仕組みです。ただし、新人エンジニアの皆さんには少し直感的に分かりにくいところがあります。手続き指向のプログラムではソースコードをざっと読めばどのような処理をしているかが一目瞭然なのですが、オブジェクト指向では一見、自分のクラスが持っていないはずのメンバーを使うことができたりするのです。そのあたりのところを今回はお話ししたいと思います。
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という英語には拡張するという意味があるのでしたね。
以下のEmployee,Engineer,Example01という3つは継承を説明するプログラムです。
package chap10;
public class Employee {
int id;
String name;
}
package chap10;
public class Engineer extends Employee {
String skill;
}
package chap10;
public class Example01 {
public static void main(String[] args) {
Engineer se = new Engineer();
se.id = 1;
se.name = "今井";
se.skill = "Java";
System.out.println("私は" + se.name + "。私のスキルは "+ se.skill + " です。");
}
}
<実行結果>
私は今井。私のスキルは Java です。 |
Employeeクラスのフィールドはidとname。Engineerクラスで宣言したフィールドはskill。EngineerクラスはEmployeeクラスをextendsしているため3つのフィールドを持っているのです。しかし、ぱっと見はクラスの持っているフィールドやメソッドが分かりにくいですね。
そこで登場するのが下図のようなクラス図というものです。

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

ただし、次のようなプログラムはコンパイルできませんので注意しましょう。
package chap10;
public class Airplane extends Vehicle {
void fly() {
System.out.println("I'm flying.");
}
}
package chap10;
public class Example03 {
public static void main(String[] args) {
Vehicle v = new Airplane();
v.fly();
}
}
このプログラムは、あたかも「乗り物よ飛べ!」といっているようなものです。
「おれ中身は飛行機だけれど、今は乗り物って呼ばれてるから飛べないんだよな」
という声が聞こえそうですね。

冗談はさておき、スーパークラス型の変数でサブクラスを扱う時、サブクラス独自のメソッドをそのままでは呼び出せません。しかし、実態は飛行機なのです。ですから、いったんは乗り物型の変数に紐づいた飛行機も、飛ばすことは可能です。プリミティブ型でもみたキャストを使います。(より厳密にはインスタンスをキャストしてるのではなく、参照をキャストしている)
以下のExample04はインスタンスのキャストの例です。
package chap10;
public class Example04 {
public static void main(String[] args) {
Vehicle v = new Airplane();
Airplane a = (Airplane) v;
a.fly();
}
}
<実行結果>
I'm flying. |
サブクラス特有のメソッドを使いたいときにはスーパークラスをサブクラスにキャストすれば可能だということですね。
ただ、これだけでは、サブクラスをスーパークラスの参照で扱えることの嬉しさがあまり伝わらないかもしれません。
4.メソッドのオーバーライド
オーバーライドとは、簡単に言うとサブクラスでスーパークラスのメソッドを作り直すことです。オーバーライドはポリモーフィズムを可能にします。ポリモーフィズムとは、同じ名前のメソッドに対して違った処理をさせることです。
以下Example05では、VehicleクラスのサブクラスであるRacingCarクラスでオーバーライドしたrun()メソッドを使っています。
package chap10;
public class RacingCar extends Vehicle {
@Override
void run() {
System.out.println("I'm running fast.");
}
}
package chap10;
public class Example05 {
public static void main(String[] args) {
Vehicle v = new RacingCar();
v.run();
}
}
<実行結果>
I'm running fast. |
ここでオーバーライドが成立するための3つの条件を挙げます。
- メソッドのシグネチャーが、スーパークラスと同じである
- メソッドのアクセス制御がスーパークラスと同じか緩い
- 戻り値の型が、スーパークラスのメソッドと同じかサブクラス型である
ひとまず3、はこの後学ぶポリモーフィズムと関連することなので、今はおいておきます。また、2、でアクセス制御を不用意に緩めるとセキュリティ問題が発生しうるのですが、本研修の範囲を超えますので割愛します。興味のある方はご自身で上記リンクをたどってください。
「1.メソッドのシグネチャが、スーパークラスと同じである」については失敗することがあります。
スペルミスによりオーバーライドのつもりがそうなっていないことが起こり得るのですね。
そのために
@Override
というアノテーション(annotation)があります。アノテーションとは注釈という意味でした。アノテーションを挿入すると「オーバーライドのつもりがスペルミスによりオーバーライドになっていなかった」という間違いを防ぐ効果があります
オーバーライドしたサブクラスのメソッドが実行されました。
では、このことがどのように役立つのでしょうか?
それは、乗り物(Vehicle)の変数ですべての種類の乗り物(車、飛行機、船)が扱えるということです。
Example06
次のExample06はそのことを説明するプログラムです。
※本来は一つのクラスに一つのJavaファイルを用意すべきですが、読みにくくなるため以下のサンプルコードは、簡易的に一つのファイルに複数のクラスを書いています。その際には、publicで宣言できるのは一つのクラスだけになります。
package chap10;
class PatrolCar extends Vehicle {
@Override
void run() {
System.out.println("I'm running.");
}
}
class Rocket extends Vehicle {
@Override
void run() {
System.out.println("I'm flying.");
}
}
class Ship extends Vehicle {
@Override
void run() {
System.out.println("I'm sailing.");
}
}
public class Example06 {
public static void main(String[] args) {
Vehicle[] vs = { new PatrolCar(), new Rocket(), new Ship(), new PatrolCar(), new Rocket() };
for (Vehicle v : vs) {
v.run();
}
}
}
<実行結果>
I'm running. I'm flying. I'm sailing. I'm running. I'm flying. |
<イメージ図>

今回のように「v.run()」という同じ命令にもかかわらず、インスタンスごとに違った処理を実行しました。このようなオブジェクト指向の仕組みをポリモーフィズム(多態性)といいます。

例えるなら、新入社員歓迎会で一発芸をやってと言われたときの対応のようなものです。「一発芸」という同じ命令でも人によってやることは異なります。
5.ポリモーフィズムで保守性が高まる
ここで、ポリモーフィズムを使わずにコーディングしたとします。何が不都合なのでしょうか?
ポリモーフィズムを使わない場合は、クラスの種類に応じて呼び出すインスタンスメソッドが変わります。そのためif文などで処理を分岐させる必要があります。クラスの種類に応じてというところは、instanceof演算子を使えばいいです。
こんな風になりますね。
<ポリモーフィズムを使わない場合>
package chap10;
class CampingCar {
void run() {
System.out.println("I'm running slowly.");
}
}
class PropellerPlane {
void run() {
System.out.println("I'm flying.");
}
}
public class Example08 {
public static void main(String[] args) {
Object[] vs = {new CampingCar(), new PropellerPlane(), new String()};
for (Object v : vs) {
if (v instanceof CampingCar) {
CampingCar c = (CampingCar) v;
c.run();
} else if (v instanceof PropellerPlane) {
PropellerPlane p = (PropellerPlane) v;
p.run();
} else {
System.out.println("想定していないクラスです");
}
}
}
}
<実行結果>
I'm running slowly. I'm flying. 想定していないクラスです |
<イメージ図>

例えば、ここで新たにSurfingクラスを作るとします。すると、“想定していないクラス”なのでmain()メソッドのif文を追加する必要が出てきてしまうのです。
しかし、以下のSurfingとExample09はポリモーフィズムを使ったコードです。乗り物の種類が増えてもmain()メソッドの中身を(ほとんど)書き換えなくて済みます。
<ポリモーフィズムを使った場合>
package chap10;
class Surfing extends Vehicle {
@Override
void run() {
System.out.println("I'm surfing.");
}
}
public class Example09 {
public static void main(String[] args) {
Vehicle[] vs = {new PatrolCar(), new Rocket(), new Ship(), new Surfing()};
for (Vehicle v : vs) {
v.run();
}
}
}
<実行結果>
I'm running. I'm flying. I'm sailing. I'm surfing. |
<イメージ図>

今動いているプログラムをいじるというのはとても勇気のいることです。もっと複雑なコードで、しかも、このmain()メソッドの処理が現役で使われている処理だとしましょう。そうするとなかなか変更するのは大変です。
ポリモーフィズムの活用の一面として、新しくクラスを追加しても変更は最低限で済むということがあるのですね。もし、あなたのプログラムがinstanceof演算子を使ってクラスの種類を判定していて、今後も判定すべきクラスが増えそうであれば、そこに不吉なにおいを嗅ぎ取ってください。
6.スーパークラスの土台の上にサブクラスは作られる
もう少し継承の仕組みのお話にお付き合いください。
以下のサンプルコードを実行したらどうなるでしょうか?
package chap10;
class Parent {
public Parent() {
System.out.println("Hello from SuperClass");
}
}
class Child extends Parent {
Child(){
}
}
public class Example12 {
public static void main(String[] args){
Child c = new Child();
}
}
<実行結果>
Hello from SuperClass |
これは、コンストラクタが継承されたわけではありません。そうではなく、見えないだけで本当は以下のようなコードなのです。見えていないコードを書き入れるとこうなっています。
package chap10;
class Parent {
public Parent() {
System.out.println("Hello from SuperClass");
}
}
class Child extends Parent {
Child(){
super();
}
}
public class Example13 {
public static void main(String[] args){
Child c = new Child();
}
}
<結果は同じ>
Childクラスのコンストラクタの先頭行でスーパークラスの引数なしのコンストラクタを呼んでいたのです。
したがって試みに3代に渡る継承関係を作るとこうなります。
package chap10;
class First {
First() {
System.out.println("Hello from First");
}
}
class Second extends First {
Second() {
super();
System.out.println("Hello from Second");
}
}
class Third extends Second {
Third() {
super();
System.out.println("Hello from Third");
}
}
public class Example14 {
public static void main(String[] args) {
Third t = new Third();
}
}
<実行結果>
Hello from First Hello from Second Hello from Third |
この順番をよくご記憶ください。スーパークラスから順番にサブクラスがコンストラクトされていってます(コンストラクターチェーンと呼ばれることもあります)。スーパークラスの土台の上にサブクラスは作られるのです。(一番下の土台はObjectクラス)
この図が理解できれば、スーパークラスの参照でサブクラスが扱えるということも理解しやすいのではないでしょうか?

7.継承は最後の手段
先にも述べたとおり、あるクラスのある機能を取り込みたいばかりに、継承を使うというのは悪い設計です。継承を使うとスーパークラスの変更がサブクラスに及ぶため、クラス間に依存関係が生じてしまうのです。
なお、依存関係が強いことを密結合、依存関係が弱いことを疎結合と情報処理の世界では呼ぶことがあります。
継承を使わなくても他のクラスの機能を取り込むことができます。その方法を「use」や「has-a」といいます。
まずは「use」を使ってみます。次のExample15を見て下さい。
package chap10;
import java.util.Scanner;
class DependencePc {
void input() {
Scanner scanner = new Scanner(System.in);
System.out.print("入力してください > ");
String inputText = scanner.nextLine();
System.out.println(inputText + "が入力されました");
}
}
public class Example15 {
public static void main(String[] args) {
DependencePc pc1 = new DependencePc();
pc1.input();
}
}
<実行結果>※キーボードからHelloと入力した場合
入力してください > Hello Helloが入力されました |
ご覧いただきたいのはDependencePc クラスのローカル変数にScannerクラスのインスタンスを割り当てている部分です。
これが「use」の関係です。
クラス図で書くと下図のようになります。

野球で例えるならピンチヒッターとして、一時的に起用された選手のような役割です。このinput()メソッドの中だけで活躍します。
クラス図ではこうなります。

もしも、ScannerクラスがPCクラスの部品と考えられて、複数のメソッドにまたがって使いたいクラスであれば、フィールドにインスタンスメンバーとして宣言するほうが良いでしょう。

野球の例えでいえばスタメンのようなものです。これが「has-a」関係です。
以下のExample16は「has-a」関係を使った例です。
package chap10;
import java.util.Scanner;
class DelegatePc {
Scanner scanner = new Scanner(System.in);
void input() {
System.out.print("入力してください > ");
String inputText = scanner.nextLine();
System.out.println(inputText + "が入力されました");
}
}
public class Example16 {
public static void main(String[] args) {
DelegatePc pc1 = new DelegatePc();
pc1.input();
}
}
<結果は上記と同じ>
ご注目いただきたいのは、Scannerクラスのインスタンスを、フィールド(インスタンス変数)として保持している部分です。そうしておいて、input()メソッドでScannerクラスのnextLine()メソッドを呼び出しています。
クラス図は以下になります。白抜きの◇があるクラスが矢印のクラスを持っているという意味です。

継承のところで見たようにクラス図の矢印はそれがどのような形のものであっても“依存”関係を表現するのでした。PcクラスはScannerクラスを持っているということは、PcクラスはScannerクラスがないと困る、すなわち依存しているというわけですね。
両方が自作のクラスであれば、矢印の先にあるクラスから作り始めたほうが簡単です。
このときnextLine()メソッドを使いたいがためだけにPcクラスがScannerクラスを継承するのは間違いです。(Scannerクラスの場合はそもそもfinalで宣言されているため継承できませんが)樹形図になっているクラスの体系をぐちゃぐちゃにしてしまいます。"is-a"関係には後ほどお話する多重継承の問題や、サブクラスにスーパークラスの不要なフィールドやメソッドまでをも組み込んでしまうという問題があります。しかし、”has-a”関係にはそのような問題はありません。
8.ClassCastException
ここでは、クラスのキャストについて見ていきます。

「サブクラスは一種のスーパークラス」でした。しかし、逆に、「スーパークラスは一種のサブクラスである」とは言えません。ですからキャストしたつもりでもスーパークラスのインスタンスをサブクラスの参照に代入することはできません。
以下のサンプルプログラムを見てください。
package chap10;
public class Example18 {
public static void main(String[] args) {
Vehicle v = new Vehicle();
Car c = (Car) v; // ClassCastException
}
}
<実行結果>
Exception in thread "main" java.lang.ClassCastException: chap10.Vehicle cannot be cast to chap10.Car(中略) |
このとき送出されるのがClassCastExceptionという例外です。
「乗り物は一種の車である」とは言えませんね。そのためこのようなキャストはできないという例外が発生したのでした。このClassCastExceptionも新人エンジニア研修ではよく見かけますのでご記憶ください。
まとめができたら、アウトプットとして演習問題にチャレンジしましょう。
以上、今回は「継承を使ってクラスをグループ化する」方法について見てきました。
次回は、「カプセル化と情報隠蔽で部品の完成度を高める」です。