# CUI_EM_OOP ミッション課題【3日目】継承・抽象クラス・インターフェース・ポリモーフィズム ## このミッションについて このミッションは、最終的に **「社員メンテナンス(ファイルシステム版)」(CUI_EM_OOP)** を完成させることを目標にした、複数回に分かれたミッション課題の**3日目**です。 **【今日からのルール変更】** 1〜3段階目(基礎確認ゾーン)は、今日からは**前回までに作ったファイル(`Book`・`Item`・`Student`・`EmployeeData`・`Main`など)を一切使わず、新しいクラスだけで完結する練習問題**にします。毎回その日の内容だけを、他の実装に引きずられずシンプルに確認できるようにするためです。(4〜6段階目「CUI_EM_OOP制作ゾーン」は、これまで通り前回までの続きから作業します。) まだ以下の内容は習っていないため使いません。今後のミッションでさらにバージョンアップしていきます。 * コレクション(`ArrayList`など) * 例外処理 * ファイルの読み書き ## 今日の講義内容 * 継承(`extends`) * `super`キーワードの構文 * `@Override`によるメソッドオーバーライド(自分で作った親クラスに対するオーバーライド) * 抽象クラスとインターフェース * ポリモーフィズムの基本(親クラス型の変数に子クラスのインスタンスを代入する意味) ## 基本の書き方(文法メモ) 今日はじめて出てくる書き方をまとめておきます。各段階で迷ったときは、ここに戻って確認してください。 ### 継承(`extends`) ```java public class 親クラス名 { // フィールド・メソッド } public class 子クラス名 extends 親クラス名 { // 親クラスのフィールド・メソッドがそのまま使える // 独自のフィールド・メソッドを追加できる } ``` 子クラスは、親クラスの`public`なフィールドやメソッドを、そのまま自分のものとして使うことができます(`private`なフィールドは、親クラスの中でしか直接アクセスできないという規則は継承していても変わりません)。 ### `super`(コンストラクタの呼び出し) ```java public class 親クラス名 { public 親クラス名(型 引数名) { // ... } } public class 子クラス名 extends 親クラス名 { public 子クラス名(型 引数名) { super(親クラスのコンストラクタに渡す値); // 必ず1行目に書く // 子クラス独自の初期化処理 } } ``` * `super(...)`は、親クラスのコンストラクタを呼び出す書き方です。書く場合は**コンストラクタの中の一番最初の行**に書く必要があります。 * 親クラスに「引数なしコンストラクタ」が1つもない場合、子クラスは必ず`super(...)`で親クラスのどのコンストラクタを呼ぶかを指定しなければ、コンパイルエラーになります。 * 逆に、親クラスに引数なしコンストラクタしかない場合(今回の`BaseData`のように、コンストラクタを1つも書いていない場合も含む)は、子クラス側で`super()`を書かなくても、Javaが自動的に親クラスの引数なしコンストラクタを呼び出してくれます。 ### `super`(メソッドの呼び出し) ```java public class 子クラス名 extends 親クラス名 { @Override public 戻り値の型 メソッド名() { 戻り値の型 親の結果 = super.メソッド名(); // オーバーライドする前の、親クラスの処理を呼び出す // 親の結果を使って、独自の処理を追加する } } ``` `super.メソッド名()`と書くと、オーバーライドする前の**親クラスが元々持っていた処理**を呼び出せます。「親の処理はそのまま活かしつつ、少しだけ追加したい」というときによく使います。(1段階目の`Circle`クラスで実際に使います) ### 抽象クラス(`abstract class`) ```java public abstract class 親クラス名 { public abstract 戻り値の型 メソッド名(); // 中身を書かない(抽象メソッド) } ``` * `abstract`をつけたクラスは、**そのままでは`new`でインスタンス化できません**。必ず`extends`した子クラスを作り、その子クラスをインスタンス化して使います。 * `abstract`をつけたメソッド(抽象メソッド)は中身(`{ }`)を書きません。「これを継承する子クラスは、必ずこのメソッドを実装(オーバーライド)してください」という約束を表します。 * 抽象メソッドを1つでも実装し忘れると、その子クラスはコンパイルエラーになります。 ### インターフェース(`interface`) ```java public interface インターフェース名 { 戻り値の型 メソッド名(); // 中身を書かない } public class クラス名 implements インターフェース名 { @Override public 戻り値の型 メソッド名() { // 実装 } } ``` * インターフェースは抽象クラスとよく似ていますが、`extends`ではなく`implements`というキーワードを使います。 * インターフェースの中に書くメソッドは、すべて中身を書かない「約束」だけのメソッドです。 * クラスの継承(`extends`)は1つの親クラスしか指定できませんが、インターフェースは`implements インターフェースA, インターフェースB`のように**複数同時に実装**できます。 ### ポリモーフィズム(親クラス型の変数に子クラスのインスタンスを代入する) ```java 親クラス名 変数名 = new 子クラス名(); ``` * 変数の**型**は「親クラス」ですが、中に入っているのは「子クラス」のインスタンスです。 * この変数からメソッドを呼び出すと、実際に代入されている**子クラス側でオーバーライドされた内容**が実行されます(親クラスの元々の中身ではありません)。 * この仕組みのおかげで、`親クラス名[]`の配列に、いろいろな種類の子クラスのインスタンスをまとめて入れて、同じように扱うことができるようになります。 * **注意**:変数の型が親クラスの場合、**親クラスに書かれているメソッドしか直接は呼び出せません**。子クラスだけが持つ独自のメソッドを呼びたい場合は「キャスト」という書き方が必要になります(7〜9段階目で扱います)。 ### `public`について これまでと同様、コンストラクタ・ゲッター・セッター・オーバーライドするメソッドは`public`で宣言してください(詳しい理由は2日目の文法メモを参照)。 ## すすめかた 1. 1段階目から順番に取り組んでください。むずかしそうに見えても、飛ばさないでください。 2. 各段階の「お題」をよく読んで、「何を作るのか」を最初に理解してから手を動かしましょう。 3. コードが書けたら、必ず実行して**「実行例」と同じ結果になるか**確認しましょう。 4. わからない言葉が出てきたら、まず自分で調べてみましょう。調べてもわからないときは、講師に質問してください。 5. コピー&ペーストではなく、自分の手で入力して理解しましょう。 ## プロジェクトの作り方 これまで使っているプロジェクトに、新しいファイルを追加していく形で進めてください。パッケージ分けはまだ行いません。 ## 提出物 * 今日修正・作成した`.java`ファイル一式 --- ## 【基礎確認ゾーン】1〜3段階目 このゾーンは、CUI_EM_OOPとは直接関係のない練習問題です。**前回までに作った`Book`・`Item`・`Student`・`EmployeeData`は一切使いません。** すべて新しいクラスを使って、今日の内容がしっかり身についているかを確認しましょう(1〜3段階目の中でお互いに作ったクラスを使い回すのはOKです)。 ### 1段階目(継承とsuperでシンプルな図形クラスを作ろう) **お題** 「図形」を表す`Shape`クラスと、それを継承した`Circle`(円)クラスを作りましょう。 **<作成フローとポイント>** 1. `Shape.java`を新規作成し、以下の内容で作成してください(コンストラクタ・メソッドはすべて`public`で宣言してください)。 * フィールド:`name`(`String`型、`private`) * コンストラクタ:引数`String name`を1つ受け取り、`name`フィールドに代入する(**引数なしコンストラクタは作りません**) * メソッド`area`:引数なし、戻り値`double`、中身は`return 0.0;`(本来は図形ごとに違う値になるはずですが、今の時点では仮の値です) * メソッド`showInfo`:引数なし、戻り値なし(`void`)、中身は`this.name + "の面積は" + this.area() + "です"`を`System.out.println`で出力する 2. `Circle.java`を新規作成し、以下の内容で作成してください。 * `Shape`クラスを継承する(`extends`) * フィールド:`radius`(`double`型、`private`) * コンストラクタ(`public`):引数`double radius`を1つ受け取る * 中で`super("円")`を呼び出してください(`Shape`に引数なしコンストラクタがないため、**この`super(...)`を書かないとコンパイルエラーになります**) * `radius`フィールドにも引数の値を代入する * `area`メソッドを`public`・`@Override`にし、`Math.PI * radius * radius`(円の面積の公式)を返すようにする * ※`Shape`側の`area`が`public`なので、オーバーライドする側も必ず`public`にする必要があります(`public`より狭い書き方をするとコンパイルエラーになります) * `showInfo`メソッドも`public`・`@Override`してください。中身は以下の2行にしてください。 * 1行目:`super.showInfo();`(オーバーライドする前の、`Shape`の`showInfo`をそのまま呼び出す) * 2行目:`"(半径:" + this.radius + ")"`を`System.out.println`で出力する 3. `Day3Check1.java`を新規作成し、`main`メソッドの中で以下を行ってください。 * `radius`が`2`の`Circle`インスタンスを作成する * 作成したインスタンスの`showInfo`メソッドを呼び出す **<実行例>** ``` 円の面積は12.566370614359172です (半径:2.0) ``` > 面積の値は小数点以下が長く表示されますが、`Math.PI`を使って正しく計算されている証拠なので、そのままでOKです。 ### 2段階目(抽象クラスとインターフェースを使ってみよう) **お題** `Shape`クラスを抽象クラスに変え、新しい図形`Rectangle`(長方形)を追加します。また、まったく別の練習として、インターフェースも使ってみましょう。 **<作成フローとポイント>** **①`Shape`を抽象クラスにする** 1. `Shape.java`を修正してください。 * クラス宣言を`public abstract class Shape`に変える * `area`メソッドを、中身のない**抽象メソッド**に変える(`public abstract double area();`) * ※`Circle`クラスはすでに`area`を`@Override`して実装済みなので、修正は不要です(そのままコンパイルが通ることを確認しましょう)。 2. `Rectangle.java`を新規作成してください。 * `Shape`クラスを継承する * フィールド:`width`、`height`(どちらも`double`型、`private`) * コンストラクタ(`public`):引数`double width, double height`を受け取り、`super("四角形")`を呼び出したうえで、2つのフィールドに代入する * `area`メソッドを`public`・`@Override`にし、`width * height`を返すようにする(`Circle`のときと同じ理由で`public`が必須です) **②インターフェースを使ってみる(①とは無関係の練習です)** 3. `Greeting.java`を新規作成し、以下の内容の**インターフェース**を作成してください。 ```java public interface Greeting { String hello(); } ``` 4. `JapaneseGreeting.java`を新規作成してください。 * `Greeting`インターフェースを`implements`する * `hello`メソッドを`public`・`@Override`にし、`"こんにちは"`を返すようにする * ※インターフェースのメソッドは元々`public`として扱われるため、実装する側も必ず`public`にする必要があります(`toString`のときと同じルールです) 5. `Day3Check2.java`を新規作成し、`main`メソッドの中で以下を行ってください。 * `width`が`3`、`height`が`4`の`Rectangle`インスタンスを作成し、`showInfo`メソッドを呼び出す * `Greeting`型の変数に`JapaneseGreeting`のインスタンスを代入し、`hello`メソッドの戻り値を出力する **<実行例>** ``` 四角形の面積は12.0です こんにちは ``` **<余裕がある人向け(任意)>** `LoudGreeting`という、もう1つのインターフェース(`String shout();`)を作り、`JapaneseGreeting`クラスに`Greeting`と`LoudGreeting`の**両方**を実装させてみましょう(`implements Greeting, LoudGreeting`)。クラスは1つしか継承できませんが、インターフェースは複数実装できることを確認してみてください。 ### 3段階目(ポリモーフィズムを体感しよう) **お題** 1〜2段階目で作った`Shape`・`Circle`・`Rectangle`を使って、「親クラス型の変数に子クラスのインスタンスを代入する」とはどういうことかを確認しましょう。 **<作成フローとポイント>** 1. `Day3Check3.java`を新規作成し、`main`メソッドの中で以下を行ってください。 * `Shape`型の変数`shape1`に、`radius`が`2`の`Circle`インスタンスを代入する(`Shape shape1 = new Circle(2);`) * `shape1`の`showInfo`メソッドを呼び出す * 変数の型は`Shape`なのに、`Circle`で上書きした`area`の内容(円の面積の計算)が使われることを確認しましょう * `Shape`型の変数`shape2`に、`width`が`3`、`height`が`4`の`Rectangle`インスタンスを代入する(`Shape shape2 = new Rectangle(3, 4);`) * `shape2`の`showInfo`メソッドを呼び出す * 同じ`Shape`型の変数なのに、今度は`Rectangle`で上書きした`area`の内容(長方形の面積の計算)が使われることを確認しましょう **<実行例>** ``` 円の面積は12.566370614359172です (半径:2.0) 四角形の面積は12.0です ``` > **ポイント①**:`shape1`と`shape2`はどちらも同じ`Shape`型の変数ですが、代入したインスタンスが`Circle`か`Rectangle`かによって、`showInfo()`の結果(特に`area()`の計算内容)が変わっています。これがポリモーフィズムの効果です。 > **ポイント②**:`Circle`は`showInfo`をオーバーライドして半径の情報を追加していますが、`Rectangle`は`showInfo`をオーバーライドしていないため、`Shape`の`showInfo`がそのまま使われています。「オーバーライドは、変えたいメソッドだけ行えばよい」ということも確認できます。 --- ## 【CUI_EM_OOP制作ゾーン】4〜6段階目 ここからは、前回までに作った`EmployeeData`・`Main`を、今日習った道具でバージョンアップしていきます。 ### 4段階目(BaseDataを作り、EmployeeDataに継承させよう) **お題** 社員データ以外のデータ(後日追加する部署データなど)も含めて共通で使える、`BaseData`という抽象クラスを作りましょう。 **<作成フローとポイント>** 1. `BaseData.java`を新規作成し、以下の内容で作成してください。 * `public abstract class BaseData`として宣言する(フィールドはありません) * 以下の3つの抽象メソッドを宣言する(すべて中身なし) * `public abstract String toString();` * `public abstract String toCSV();` * `public abstract String getPrimaryKeyValue();` * ※`toString`は`Object`クラスのメソッドを上書きする形にもなりますが、ここではまだ中身のない抽象メソッドとして宣言するだけなので、`@Override`はつけなくて構いません(つけても間違いではありませんが、今回は付けない形で統一します)。 2. `EmployeeData.java`を修正し、`BaseData`クラスを継承してください(`public class EmployeeData extends BaseData`)。 * 2日目にすでに`@Override public String toString()`を作成済みなので、そのままで`BaseData`の`toString`の約束を満たしています(修正不要です)。 * `EmployeeData`のコンストラクタに`super(...)`を書く必要は**ありません**。`BaseData`にはコンストラクタを1つも書いていないため、Javaが自動的に引数なしの親コンストラクタを呼び出してくれるためです。 3. `EmployeeData.java`に、以下の2つのメソッドを追加してください(どちらも`public`・`@Override`)。 * `toCSV`:引数なし、戻り値`String`。6つのフィールドの値を、ゲッターを使ってカンマ区切り(`","`)でつなげて`return`する * 例:`getIdEmployee() + "," + getNmEmployee() + ...` * `getPrimaryKeyValue`:引数なし、戻り値`String`。`idEmployee`フィールドの値をそのまま`return`する 4. `Main.java`で、4段階目(1日目)から作っている`employee`変数を使って、`employee.toCSV()`と`employee.getPrimaryKeyValue()`の結果をそれぞれ出力し、正しく動いているか確認してください。 **<実行例>** ``` 10000,山田龍也,ヤマダタツヤ,yamada@hoge.jp,aaa,10 10000 ``` (出力の形式は自由です。上の例のように2行に分けて出力すればOKです。) ### 5段階目(Mainでポリモーフィズムを使おう) **お題** `Main.java`の中で、`EmployeeData`型を使っていた変数・配列を、`BaseData`型に変更してみましょう。 **<作成フローとポイント>** 1. `Main.java`を見直し、以下の宣言を変更してください。 * `EmployeeData employee = ...` → `BaseData employee = ...` * `EmployeeData[] employees = new EmployeeData[5];` → `BaseData[] employees = new BaseData[5];` * ※右辺で`new EmployeeData(...)`しているところは変更不要です。「`EmployeeData`のインスタンスを、`BaseData`型の変数・配列に入れる」という変更です。 2. 変更すると、`isMatchId`メソッドを呼び出している箇所が、**すべてコンパイルエラーになります**。`BaseData`型には`isMatchId`というメソッドが存在しないためです。呼び出し箇所は複数あるので、探し忘れに注意してください。 * 1日目7段階目で書いた動作確認用の呼び出し(`employees[2].isMatchId("10002")`など) * 1日目8〜9段階目で書いた検索処理の中の呼び出し * これらの`何か.isMatchId(引数)`という呼び出しを、すべて`何か.getPrimaryKeyValue().equals(引数)`という書き方に直してください。 * 例:`employees[2].isMatchId("10002")` → `employees[2].getPrimaryKeyValue().equals("10002")` * 検索処理の中の呼び出し(`employees[i].isMatchId(入力されたID)`のような形。`入力されたID`の部分は、8段階目で`Scanner`から読み取った自分がつけた変数名に置き換えてください)→ `employees[i].getPrimaryKeyValue().equals(入力されたID)` * `getPrimaryKeyValue`は`BaseData`で約束されているメソッドなので、`BaseData`型のままでも呼び出せます。 * `isMatchId`メソッド自体は`EmployeeData.java`に残しておいて構いません(今後使わなくなりますが、消さなくても動作に影響はありません)。 3. `Main.java`のすべての動作(全件表示・ID検索など)が、今まで通り同じ結果になることを確認してください。 **<実行例>** これまでと表示内容は変わりません。1日目の実行例、2日目6段階目の実行例と見比べて確認してください。 > **ポイント**:`isMatchId`は`EmployeeData`専用のメソッドでしたが、`getPrimaryKeyValue`は`BaseData`を継承したクラスなら何でも使える共通の仕組みです。今回のように「専用メソッド」を「共通の仕組み」に置き換えられるのが、抽象クラスとポリモーフィズムの強みです。 ### 6段階目(DepartmentDataを作って、ポリモーフィズムの威力を実感しよう) **お題** 社員データとは別に、部署データを表す`DepartmentData`クラスを作り、`EmployeeData`と一緒くたに扱えることを確認しましょう。 **<作成フローとポイント>** 1. `DepartmentData.java`を新規作成し、`EmployeeData`と同じ要領で以下の内容で作成してください。 * `BaseData`クラスを継承する * フィールド(すべて`private`・`String`型):`idDepartment`(部署ID)、`nmDepartment`(部署名) * 2つのフィールドそれぞれにゲッター・セッターを作成する(`getIdDepartment`/`setIdDepartment`、`getNmDepartment`/`setNmDepartment`) * コンストラクタは何も書かなくて構いません(Javaが自動的に用意してくれる「引数なし・中身なし」のコンストラクタをそのまま使います) * 以下の3つのメソッドを`public`・`@Override`で作成する * `toString`:`getIdDepartment() + "\t" + getNmDepartment()`を返す * `toCSV`:`getIdDepartment() + "," + getNmDepartment()`を返す * `getPrimaryKeyValue`:`getIdDepartment()`を返す 2. `Main.java`に、以下のデータをすべて格納できる`BaseData`型の配列`allData`を用意してください(`BaseData[] allData = new BaseData[11];`)。 * `allData[0]`〜`allData[4]`:5段階目で`BaseData`型に変えた`employees`配列の中身(`employees[0]`〜`employees[4]`)をそのまま代入する * `allData[5]`〜`allData[10]`:以下の部署データを、それぞれ新しく`DepartmentData`インスタンスを作成して代入する ``` 10,役員 11,人事・総務部 12,経理部 13,システム開発事業部 14,教育事業部 15,営業企画部 ``` **<部署データを入れるときの注意>** `allData`は`BaseData`型の配列です。`allData[5] = new DepartmentData();`のように**先に配列へ代入してしまうと**、そのあとで`allData[5].setIdDepartment(...)`のような`DepartmentData`専用のセッターを呼び出せなくなります(`BaseData`型にはセッターの約束がないためです)。必ず次の順番で進めてください。 1. `DepartmentData`型の変数(例:`dept`)を用意し、`new DepartmentData()`でインスタンスを作成する 2. `dept`のセッターを使って、部署ID・部署名を設定する 3. 設定が終わった`dept`を`allData[5]`のように代入する 3. `for`文を使って、`allData`の中身を順番に`System.out.println`で出力してください。 **<実行例>** ``` 10000 山田龍也 ヤマダタツヤ yamada@hoge.jp aaa 10 10001 高橋準 タカハシジュン jtaka@hoge.jp bbb 10 10002 麻枝史明 マエダフミアキ fmak@hoge.jp ccc 11 10003 丸戸章介 マルトショウスケ marusyo@hoge.jp ddd 12 10004 三宅武 ミヤケタケシ miya@hoge.jp eee 13 10 役員 11 人事・総務部 12 経理部 13 システム開発事業部 14 教育事業部 15 営業企画部 ``` > **ポイント**:`allData`は`BaseData`型の配列ですが、中身は`EmployeeData`と`DepartmentData`という**まったく別のクラス**のインスタンスが混ざっています。それでも同じ`for`文・同じ`System.out.println`で、それぞれ正しい形式で表示できました。これがポリモーフィズムの一番の効果です。 --- ## 【挑戦問題ゾーン】7〜9段階目 ここからは応用問題です。今日習った内容に加えて、自分で少し調べながら進める部分があります。難しく感じても焦らず、じっくり調べながら取り組んでみましょう(経験者の方でも、多少時間がかかる内容です)。 ### 7段階目(instanceofで型を確認しよう) **お題** 6段階目で作った`allData`配列の中身が、実際には`EmployeeData`と`DepartmentData`のどちらなのかを調べる方法を調査し、それぞれの件数を数えてください。 **<お題の条件>** * `allData`を`for`文で1件ずつ確認し、その要素が実際には`EmployeeData`のインスタンスなのか、`DepartmentData`のインスタンスなのかを判定する * 社員データの件数と、部署データの件数を、それぞれ数えて出力する **<考え方のヒント>** * 「ある変数の中身が、実際にはどのクラスのインスタンスか」を調べる演算子があります。「Java instanceof」で調べてみましょう。 **<実行例>** ``` 社員データ:5件 部署データ:6件 ``` ### 8段階目(キャストでサブクラス限定の機能を使おう) **お題** `allData`は`BaseData`型の配列なので、そのままでは`EmployeeData`だけが持っている`getMailAddress`メソッドを呼び出せません。「キャスト」という方法を調べて、全社員のメールアドレスを表示してください。 **<お題の条件>** * `allData`を`for`文で1件ずつ確認し、7段階目の`instanceof`を使って`EmployeeData`かどうかを判定する * `EmployeeData`だった場合のみ、`EmployeeData`型に**キャスト**したうえで`getMailAddress`メソッドを呼び出し、結果を出力する **<考え方のヒント>** * 「親クラス型の変数の中身を、実際の子クラスの型として扱いなおす」書き方を「Java キャスト」で調べてみましょう。書き方の形は`(型名) 変数名`です。 * `instanceof`で確認せずにキャストすると、実際の型と違う型にキャストしようとして`ClassCastException`というエラーになることがあります。必ず`instanceof`で確認してからキャストしましょう。 **<実行例>** ``` yamada@hoge.jp jtaka@hoge.jp fmak@hoge.jp marusyo@hoge.jp miya@hoge.jp ``` ### 9段階目(部署名から社員を検索する集計プログラムを作ろう) **お題** 7〜8段階目で調べた`instanceof`とキャストを組み合わせて、「部署名」から「その部署に所属する社員」を検索するプログラムを作ってください。 **<お題の条件>** 1. `allData`の中から、`nmDepartment`が`"システム開発事業部"`である`DepartmentData`を探し、その`idDepartment`(部署ID)を調べる 2. 1で調べた部署IDと、`idDepartment`が一致する`EmployeeData`を`allData`の中からすべて探す 3. 見つかった社員の`nmEmployee`(社員名)と`mailAddress`(メールアドレス)を、タブ区切りで出力する ※`nmDepartment`・`idDepartment`・`nmEmployee`・`mailAddress`はすべて`private`なフィールドなので、実際にはそれぞれのゲッター(`getNmDepartment()`など)を使って値を取得してください。 **<実行例>** ``` 三宅武 miya@hoge.jp ``` **<余裕がある人向け(任意)>** 部署名を`"システム開発事業部"`のように固定で書くのではなく、`Scanner`を使ってキーボードから入力できるようにしてみましょう。存在しない部署名が入力された場合の表示(例:「該当する部署が見つかりませんでした」)も考えてみてください。 --- ## まとめ 今日は「継承」「`super`」「抽象クラス」「インターフェース」「ポリモーフィズム」という、オブジェクト指向の中心となる仕組みを学びました。`EmployeeData`は`BaseData`を継承する形になり、`DepartmentData`という仲間も加わって、`BaseData`型でまとめて扱えるようになりました。これは最終的に完成するシステムの土台となる、とても重要な変更です。 今日書いたコードも消さずに残しておいてください。次回以降のミッションで、コレクション・例外処理・ファイル入出力などを習いながら、最終的に本物の「社員メンテナンス(ファイルシステム版)」システムに近づけていきます。