1日目のゴール
継承アプリ1日目のテーマは
「共通部分をまとめる」=継承の入口を“安全に”体験すること です。
今日は、
「継承って便利そうだけど、どこが嬉しくて、どこが危険なのか」
を、まず“共通部分をまとめる”という一番シンプルなところから
かみ砕いて感じてもらいます。
継承って、そもそも何をする仕組み?
「似たクラスの共通部分を、親クラスにまとめる」
継承(extends)は、ざっくり言うとこういう仕組みです。
似たようなクラスがいくつもあるとき、
その「共通している部分」を親クラスにまとめて、
子クラスから再利用できるようにする。
イメージで言うと、
「犬」「猫」「鳥」みたいなクラスがあって、
どれも「名前」と「年齢」を持っているなら、
それを「動物」という親クラスにまとめる。
コードで見ると、こんな感じです。
public class Animal {
protected String name;
protected int age;
public void introduce() {
System.out.println("名前: " + name + ", 年齢: " + age);
}
}
Javapublic class Dog extends Animal {
public void bark() {
System.out.println("ワン!");
}
}
Javapublic class Cat extends Animal {
public void meow() {
System.out.println("ニャー");
}
}
JavaDog と Cat は、Animal の「名前」「年齢」「自己紹介メソッド」を
そのまま使えます。
「共通部分を親にまとめて、子から使う」
これが継承の基本の形です。
まずは“継承なし”で書いてみる
同じようなコードが増えていく感覚をあえて味わう
継承のありがたみを感じるには、
まず「継承なし」で書いてみるのが一番です。
例えば、「社員」と「アルバイト」を表すクラスを考えます。
public class Employee {
private String name;
private int age;
private String employeeId;
public Employee(String name, int age, String employeeId) {
this.name = name;
this.age = age;
this.employeeId = employeeId;
}
public void introduce() {
System.out.println("社員: " + name + " (" + age + "歳, ID: " + employeeId + ")");
}
}
Javapublic class PartTimer {
private String name;
private int age;
private String partTimerId;
public PartTimer(String name, int age, String partTimerId) {
this.name = name;
this.age = age;
this.partTimerId = partTimerId;
}
public void introduce() {
System.out.println("アルバイト: " + name + " (" + age + "歳, ID: " + partTimerId + ")");
}
}
Javaここで感じてほしいのは、
名前と年齢のフィールドがほぼ同じ
コンストラクタの前半もほぼ同じintroduce() のロジックもかなり似ている
という 「似たコードが増えていく感じ」 です。
このまま増やしていくと、
契約社員
派遣社員
インターン
などが増えるたびに、
同じようなフィールドとメソッドを
何度も書くことになります。
共通部分を見つける:何が“同じ顔”をしているか
「人としての情報」と「雇用形態としての情報」を分ける
ここで、一歩立ち止まって
「何が共通で、何が違うのか」を言葉にしてみます。
共通しているもの:
名前
年齢
違っているもの:
社員ID
アルバイトID
表示文言(社員かアルバイトか)
つまり、
「人としての情報」と
「雇用形態としての情報」が混ざっている」
状態です。
この「人としての共通部分」を
親クラスにまとめるのが、
継承の一番素直な使い方です。
継承で“人としての共通部分”をまとめる
Person を親クラスにして、Employee / PartTimer を子にする
まず、共通部分だけを持つ Person を作ります。
public class Person {
protected String name;
protected int age;
public Person(String name, int age) {
if (age < 0) {
throw new IllegalArgumentException("年齢は0以上");
}
this.name = name;
this.age = age;
}
public void introduce() {
System.out.println("名前: " + name + " (" + age + "歳)");
}
}
Javaここでは、
名前と年齢のフィールド
年齢のチェック
自己紹介の基本形
をまとめています。
次に、Employee を継承で書き直します。
public class Employee extends Person {
private String employeeId;
public Employee(String name, int age, String employeeId) {
super(name, age); // Person のコンストラクタを呼ぶ
this.employeeId = employeeId;
}
@Override
public void introduce() {
System.out.println("社員: " + name + " (" + age + "歳, ID: " + employeeId + ")");
}
}
JavaPartTimer も同様です。
public class PartTimer extends Person {
private String partTimerId;
public PartTimer(String name, int age, String partTimerId) {
super(name, age);
this.partTimerId = partTimerId;
}
@Override
public void introduce() {
System.out.println("アルバイト: " + name + " (" + age + "歳, ID: " + partTimerId + ")");
}
}
Javaここで起きていることを整理すると、
名前と年齢のフィールドは Person に移った
年齢チェックも Person に移った
Employee / PartTimer は「雇用形態としての違い」だけを持つようになった
つまり、
「共通部分を親にまとめて、違いだけを子に残す」
という継承の基本形ができています。
重要ポイントの深掘り1:super と「親の責務」
親に任せるところと、子で上書きするところ
継承でよく出てくるキーワードが super です。
super(name, age); は、
「親クラスのコンストラクタを呼ぶ」 という意味です。
ここで大事なのは、
「名前と年齢の初期化」と
「年齢のチェック」という責務を
Person に任せていること。
Employee や PartTimer は、
「自分の ID をどう持つか」だけに集中できる。
さらに、@Override でintroduce() を上書きしているところもポイントです。
親の introduce() は「基本形」。
子の introduce() は「雇用形態ごとの具体形」。
「共通の振る舞いは親に置き、
必要なら子で上書きする」
これが継承のもう一つの顔です。
重要ポイントの深掘り2:継承は“なんでもまとめる魔法”ではない
「共通しているから親にする」は危険なときもある
ここで、継承の“危険な側面”も少し触れておきます。
継承は便利ですが、
「共通しているからとりあえず親にまとめる」
という使い方をすると、すぐに苦しくなります。
例えば、
「社員」と「商品」が
どちらも「名前」を持っているからといって、
同じ親クラスにしてしまうのは変です。
共通しているのは「フィールド名」だけで、
意味はまったく違うからです。
継承でまとめていいのは、
「同じ種類のもの」
「同じ世界の中のもの」
だけです。
Employee と PartTimer は
「人間であり、雇用形態が違う」という関係なので
Person を親にするのは自然です。
でも、
User と File と Order を
「全部 ID を持っているから」といって
同じ親にするのは不自然です。
「共通している“意味”があるか?」
を見てから、継承を使うかどうか決める。
これが、継承を安全に使うための大事な視点です。
例題:通知クラスで“共通部分をまとめる”
メール通知とプッシュ通知の共通部分
もう一つ、継承の基本形を感じる例を出します。
「通知」を表すクラスを考えます。
メール通知:
宛先メールアドレス
件名
本文
プッシュ通知:
宛先ユーザーID
タイトル
本文
まず、継承なしで書いてみます。
public class MailNotification {
private String toAddress;
private String subject;
private String body;
public void send() {
System.out.println("メール送信: " + toAddress + " / " + subject);
}
}
Javapublic class PushNotification {
private String userId;
private String title;
private String body;
public void send() {
System.out.println("プッシュ送信: " + userId + " / " + title);
}
}
Javaここで共通部分を探します。
「通知」という意味で共通しているもの:
本文(body)
送るという行為(send)
違っているもの:
宛先の種類(メールアドレスかユーザーIDか)
表示文言
そこで、親クラス Notification を作ります。
public abstract class Notification {
protected String body;
public Notification(String body) {
this.body = body;
}
public abstract void send();
}
JavaMailNotification はこう。
public class MailNotification extends Notification {
private String toAddress;
private String subject;
public MailNotification(String toAddress, String subject, String body) {
super(body);
this.toAddress = toAddress;
this.subject = subject;
}
@Override
public void send() {
System.out.println("メール送信: " + toAddress + " / " + subject);
System.out.println("本文: " + body);
}
}
JavaPushNotification はこう。
public class PushNotification extends Notification {
private String userId;
private String title;
public PushNotification(String userId, String title, String body) {
super(body);
this.userId = userId;
this.title = title;
}
@Override
public void send() {
System.out.println("プッシュ送信: " + userId + " / " + title);
System.out.println("本文: " + body);
}
}
Javaここでも、
「通知としての共通部分(本文と send という行為)」を親にまとめて、
「通知の種類ごとの違い」を子に残す。
という継承の基本形が見えます。
1日目で本当に掴んでほしいこと
継承アプリ1日目で伝えたいのは、
「継承は“共通部分をまとめる道具”だけど、
まとめるのは“意味が共通している部分”だけ」
という感覚です。
似たクラスが増えてきたときに、
何が同じで、何が違うのか
同じ部分は「同じ世界のもの」か
それを親クラスにまとめると、読みやすく・直しやすくなるか
を考えてから、extends を使う。
「共通部分をまとめる」は、
継承の入り口として一番分かりやすいテーマですが、
同時に「意味を見てからまとめる」という
設計の目も育ててくれます。
次のステップでは、
この「共通部分をまとめる」から
「共通のインターフェースを持つ」「多態性(ポリモーフィズム)」
へとつなげていけます。


