32 Commits

Author SHA1 Message Date
m-shimizu
99506c4369 Day3_9段階目 2026-09-24 15:57:49 +09:00
m-shimizu
5a8c7eb2fd Day3_7段階目+8段階目 2026-09-24 15:17:33 +09:00
m-shimizu
a8dc3e82e6 Day3_6段階目 2026-09-24 14:37:36 +09:00
m-shimizu
491415d6ee Day3_5段階目 2026-09-24 12:58:42 +09:00
m-shimizu
a944d06928 Day3_4段階目 2026-09-24 11:00:51 +09:00
m-shimizu
3fd65f6437 Day2_9段階目 2026-09-24 10:10:55 +09:00
m-shimizu
64bd81c717 Day2_8段階目 2026-09-24 09:59:31 +09:00
m-shimizu
41bf9831a8 Day2_6段階目 2026-09-18 15:57:50 +09:00
m-shimizu
f73b4054b3 Day2_4段階目 2026-09-18 14:23:01 +09:00
m-shimizu
8a75a03511 Day1_9段階目 2026-09-18 14:03:59 +09:00
m-shimizu
5095e0fae1 Day1_8段階目 2026-09-18 13:17:47 +09:00
m-shimizu
6000722eeb Day1_7段階目 2026-09-18 12:32:41 +09:00
m-shimizu
d8d13385fe Day1_5段階目 2026-09-18 11:05:58 +09:00
m-shimizu
7e13efcb68 3段階目 2026-09-18 10:12:01 +09:00
m-shimizu
ba035d4a3c 2段階目 2026-09-16 16:47:52 +09:00
s-ohasi
e28c819868 Merge branch 'main' of https://gitea.it-college.co.jp/2026_0910_AI_5G_Java_MUN/java-cui-em-oop 2026-09-16 16:35:52 +09:00
s-ohasi
48af3a3a99 第一段階完了しました。 2026-09-16 16:35:38 +09:00
m-shimizu
86a51ddb8e 1段階目 2026-09-16 16:07:25 +09:00
m-shimizu
23f5358254 3段階目 2026-09-16 13:42:09 +09:00
m-shimizu
b3fd66606d Merge branch 'main' of https://gitea.it-college.co.jp/2026_0910_AI_5G_Java_MUN/java-cui-em-oop into m_shimizu 2026-09-16 13:12:42 +09:00
m-shimizu
2ed5d89eca 2段階目 2026-09-16 13:12:13 +09:00
m-mun
e9aa2aa4e7 3日目のミッション課題を格納しました 2026-09-16 12:26:30 +09:00
m-shimizu
5eebc7c766 1段階目 2026-09-15 16:25:25 +09:00
m-shimizu
ac9efcc611 6段階目 2026-09-15 15:54:40 +09:00
s-ohasi
542af2a5d4 2段階目完了しました。 2026-09-15 14:58:52 +09:00
m-shimizu
2065f850b4 段階4 2026-09-15 14:54:00 +09:00
m-shimizu
389c1a0a48 段階3 2026-09-15 14:26:28 +09:00
m-shimizu
357ad900a1 段階2 2026-09-15 13:51:34 +09:00
m-mun
e7a1d9851d 2日目のミッション課題を追加しました 2026-09-15 12:32:46 +09:00
s-ohasi
e6753a16f4 2段階目完了しました。 2026-09-14 17:19:34 +09:00
7559a7dfa7 段階2完了いたしました。 2026-09-14 16:36:18 +09:00
43567c0e0c 1段階目完了 2026-09-14 15:14:17 +09:00
23 changed files with 1667 additions and 9 deletions

6
.classpath Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="src" path="em-oop/src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ hs_err_pid*
replay_pid*
/.metadata/
/bin/

17
.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>java-cui-em-oop</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,388 @@
# CUI_EM_OOP ミッション課題【2日目】コンストラクタ・カプセル化・static
## このミッションについて
このミッションは、最終的に **「社員メンテナンス(ファイルシステム版)」(CUI_EM_OOP)** を完成させることを目標にした、複数回に分かれたミッション課題の**2日目**です。
**前回(1日目)作成したプロジェクトのファイルを、そのまま引き続き使います。** 同じプロジェクトに追記・修正を加えていってください(`Book`、`Student`、`Item`、`EmployeeData`、`Main`、`BasicCheck1〜3`など)。
今日は、1日目に作った「まだ簡易版」のクラスたちを、今日習う道具を使って、完成形にぐっと近づけていきます。それでも、まだ以下の内容は習っていないため使いません。今後のミッションでさらにバージョンアップしていきます。
* 継承・多態性(`extends`、抽象クラス)
* コレクション(`ArrayList`など)
* 例外処理
* ファイルの読み書き
## 今日の講義内容
* コンストラクタ
* コンストラクタのオーバーロード
* メソッドのオーバーライド(`Object`クラスの`toString`など)
* アクセス修飾子
* ゲッター・セッター
* `static`、静的メンバ
## 基本の書き方(文法メモ)
今日はじめて出てくる書き方をまとめておきます。各段階で迷ったときは、ここに戻って確認してください。
### コンストラクタ
```java
public class クラス名 {
public クラス名() {
// インスタンスが作られたときに実行される処理(何もしなくてもOK)
}
public クラス名(型 引数名, 型 引数名) {
// 引数を使ってフィールドを設定する処理
}
}
```
* コンストラクタの名前は、**必ずクラス名とまったく同じ**にします。
* コンストラクタには**戻り値の型を書きません**(`void`も書きません)。ここがメソッドとの大きな違いです。
* `new クラス名();`と書けば引数なしのコンストラクタが、`new クラス名(値, 値);`のように引数を渡せば対応する引数ありのコンストラクタが呼ばれます。渡す引数の数や型によって、Javaが自動的にどちらを実行するか選んでくれます。これを**コンストラクタのオーバーロード**と呼びます。
* **注意**:Javaは本来、コンストラクタを1つも書かないクラスに対して「何もしない引数なしコンストラクタ」を自動的に用意してくれます。しかし、コンストラクタを1つでも自分で書くと、この自動生成は行われなくなります。そのため、引数なしでの生成もこれまで通り使いたい場合は、引数なしコンストラクタも自分で明示的に書く必要があります(5段階目で2つとも作るのはこのためです)。
### アクセス修飾子(`private`)
フィールドに`private`をつけると、そのフィールドは**自分のクラスの中からしか直接アクセスできなく**なります。クラスの外からは、これから作るゲッター・セッターというメソッドを経由してアクセスすることになります。
### ゲッター・セッターの命名ルール
* ゲッター:`get` + フィールド名(先頭を大文字にしたもの)というメソッド名にし、フィールドの値を`return`する。引数はなし。
* 例:フィールド`price`(`int`型) → `public int getPrice() { return this.price; }`
* セッター:`set` + フィールド名(先頭を大文字にしたもの)というメソッド名にし、引数で受け取った値をフィールドに代入する。戻り値は`void`。
* 例:フィールド`price`(`int`型) → `public void setPrice(int price) { this.price = price; }`
* 自分のクラスの中から自分のゲッター・セッターを呼び出すときは、`this.getPrice()`のように書いても、`this.`を省略して`getPrice()`のように書いても、どちらも同じ意味になります。
### `static`(静的メンバ)
フィールドやメソッドに`static`をつけると、そのフィールド・メソッドは**インスタンスごとではなく、クラス全体で1つだけ共有される**ものになります。
```java
public class クラス名 {
static 型 フィールド名;
public static 戻り値の型 メソッド名() {
return フィールド名;
}
}
```
呼び出すときは、インスタンス経由ではなく`クラス名.メソッド名()`のように、**クラス名から直接**呼び出します。
### メソッドのオーバーライドと`@Override`
すべてのクラスは、`Object`という共通のクラスを自動的に受け継いでいます(`extends`と書いていないクラスも、実は見えないところで`Object`を継承しています)。そのため、どのクラスも`toString`のようなメソッドを最初から持っています。この中身を自分のクラス用に書き直すことを**オーバーライド(Override)**と呼びます。
* オーバーライドするメソッドの前には`@Override`をつけましょう。これは「このメソッドは親のメソッドを上書きしていますよ」とJavaに伝えるための目印です。書き忘れても動作は変わりませんが、書いておくとメソッド名のスペルミスなどにJavaが気づいてくれるので、必ずつける習慣にしましょう。
* **重要**:`toString`は`Object`クラスの中で`public`として宣言されています。オーバーライドする側は、元のメソッドより公開範囲を狭くすることができないというルールがあるため、**オーバーライドする`toString`メソッドには必ず`public`をつけてください**(`public`をつけずに書くと、コンパイルエラーになります)。
### 「オーバーロード」と「オーバーライド」は別物です
名前が似ていて混同しやすいので、ここで整理しておきます。
* **オーバーロード(Overload)**:同じクラスの中に、同じ名前だけど引数の型や数が違うメソッド(コンストラクタ)を複数作ること。今日の`Book`のコンストラクタが例です。
* **オーバーライド(Override)**:親のクラス(`Object`など)がもともと持っているメソッドを、自分のクラスで上書きして中身を変えること。今日の`toString`が例です。
### `public`をつける習慣について
コンストラクタ・ゲッター・セッターは、特に指定がない限り`public`で宣言してください。今はすべてのクラスを同じ場所(パッケージ)に置いているので`public`をつけなくても動いてしまいますが、後日パッケージ分けをしたときに「他のクラスから呼び出せない」という問題が起きないよう、今のうちから`public`をつける習慣にしておきましょう。
## すすめかた
1. 1段階目から順番に取り組んでください。むずかしそうに見えても、飛ばさないでください。
2. 各段階の「お題」をよく読んで、「何を作るのか」を最初に理解してから手を動かしましょう。
3. コードが書けたら、必ず実行して**「実行例」と同じ結果になるか**確認しましょう。
4. わからない言葉が出てきたら、まず自分で調べてみましょう。調べてもわからないときは、講師に質問してください。
5. コピー&ペーストではなく、自分の手で入力して理解しましょう。
## 提出物
* 今日修正・作成した`.java`ファイル一式
---
## 【基礎確認ゾーン】1〜3段階目
このゾーンは、CUI_EM_OOPとは直接関係のない練習問題です。1日目に作った`Book`・`Item`・`Student`クラスを、今日習った道具でバージョンアップしながら、基本がしっかり身についているかを確認しましょう。
### 1段階目(Bookクラスにコンストラクタを作ろう)
**お題**
`Book`クラスに、コンストラクタを2つ(オーバーロード)追加してください。コンストラクタは2つとも`public`で宣言してください。
**<作成フローとポイント>**
1. `Book.java`に、以下の**引数なしコンストラクタ**を追加してください。
* `title`に`"タイトル未設定"`、`author`に`"著者未設定"`、`price`に`0`を代入する
2. `Book.java`に、以下の**3つの引数を持つコンストラクタ**を追加してください(1と合わせて2つのコンストラクタになります=コンストラクタのオーバーロード)。
* 引数:`String title, String author, int price`
* 処理:渡された3つの引数を、それぞれ対応するフィールドに代入する
3. `BasicCheck1.java`を修正し、`main`メソッドの中で以下を行ってください。
* 引数なしコンストラクタを使って`book1`を作成し、そのまま`title`・`author`・`price`を出力する
* 3つの引数を持つコンストラクタを使って、`book2`を`"銀河鉄道の夜"`・`"宮沢賢治"`・`500`で作成し、同様に出力する
**<実行例>**
```
タイトル未設定
著者未設定
0
銀河鉄道の夜
宮沢賢治
500
```
### 2段階目(Itemクラスをカプセル化し、toStringをオーバーライドしよう)
**お題**
`Item`クラスのフィールドを`private`にして、ゲッター・セッターを作成しましょう。また、`Object`クラスから受け継いでいる`toString`メソッドをオーバーライドしてみましょう。
**<作成フローとポイント>**
1. `Item.java`の3つのフィールド(`name`、`price`、`stock`)に、すべて`private`をつけてください。
2. 3つのフィールドそれぞれに、ゲッター・セッターを作成してください。
* `getName()` / `setName(String name)`
* `getPrice()` / `setPrice(int price)`
* `getStock()` / `setStock(int stock)`
3. `sell`メソッドの中身を、フィールドに直接アクセスする書き方(`this.stock = this.stock - amount;`)から、作成したゲッター・セッターを使う書き方に直してください。
* 例:`this.setStock(this.getStock() - amount);`
4. `Item.java`に、以下のメソッドを追加してください。
* アクセス修飾子:`public`(**必須**です。理由は冒頭の「基本の書き方(文法メモ)」を参照してください)
* `@Override`をつける
* メソッド名:`toString`
* 引数:なし
* 戻り値:`String`
* 中身:`getName() + " " + getPrice() + "円 (残り" + getStock() + "個)"` のような文字列を`return`する
5. `BasicCheck3.java`を修正してください。
* `items[i].name = ...`のようにフィールドへ直接代入していた箇所を、すべて`items[i].setName(...)`のようにセッター経由に書き換える
* 出力部分を、`System.out.println(items[i]);`のように、**オブジェクトをそのまま渡す形**に書き換える
* ※`System.out.println`は、渡されたオブジェクトの`toString`メソッドの戻り値を自動的に出力してくれます。
**<実行例>**
```
ノート 120円 (残り45個)
消しゴム 80円 (残り25個)
鉛筆 60円 (残り95個)
```
### 3段階目(Studentクラスにコンストラクタとstaticメンバを追加しよう)
**お題**
`Student`クラスにコンストラクタを追加し、「これまで何人の生徒が作られたか」を数える仕組みを`static`で作ってみましょう。
**<作成フローとポイント>**
1. `Student.java`に、以下のコンストラクタを追加してください。
* 引数:`int studentId, String name`
* 処理:渡された引数を、それぞれ対応するフィールドに代入する
2. `Student.java`に、以下の`static`フィールドを追加してください。
* `static int studentCount = 0;`(生徒が何人作られたかを数えるための、クラス全体で共有するフィールド)
3. 2で作ったコンストラクタの中で、`studentCount`を1増やす処理を追加してください(`studentCount++;`)。
4. `Student.java`に、以下の`static`メソッドを追加してください。
* メソッド名:`getStudentCount`
* 引数:なし
* 戻り値:`int`
* 中身:`studentCount`の値を`return`する
5. `BasicCheck2.java`を修正してください。
* コンストラクタを使って、`student1`(`1`, `"鈴木一郎"`)、`student2`(`2`, `"田中花子"`)、`student3`(`3`, `"佐藤次郎"`)の3人分のインスタンスを作成する
* それぞれの`printProfile`メソッドを呼び出す
* 最後に、`Student.getStudentCount()`のように**インスタンスではなくクラス名から直接**呼び出し、結果を`"現在の生徒数:" + 件数 + "人"`という形式で出力する
**<考え方のヒント>**
* `static`がついたフィールドやメソッドは、`this.name`のような個々のインスタンスが持つデータとは違い、**そのクラス全体で1つだけ共有される**データです。だからこそ、インスタンスを経由せず`Student.getStudentCount()`のようにクラス名から直接呼び出せます。
**<実行例>**
```
学籍番号:1 名前:鈴木一郎
学籍番号:2 名前:田中花子
学籍番号:3 名前:佐藤次郎
現在の生徒数:3人
```
---
## 【CUI_EM_OOP制作ゾーン】4〜6段階目
ここからは、1日目に作った`EmployeeData`・`Main`を、今日習った道具でバージョンアップし、完成形の姿にぐっと近づけていきます。
### 4段階目(EmployeeDataをカプセル化しよう)
**お題**
`EmployeeData`クラスの6つのフィールドを`private`にして、ゲッター・セッターを作成しましょう。
**<作成フローとポイント>**
1. `EmployeeData.java`の6つのフィールドすべてに、`private`をつけてください。
2. 6つのフィールドそれぞれに、以下の名前でゲッター・セッターを作成してください(今後も同じ名前を使い続けるので、統一しておきましょう。すべて`public`で宣言してください)。
* `getIdEmployee()` / `setIdEmployee(String idEmployee)`
* `getNmEmployee()` / `setNmEmployee(String nmEmployee)`
* `getKnEmployee()` / `setKnEmployee(String knEmployee)`
* `getMailAddress()` / `setMailAddress(String mailAddress)`
* `getPassword()` / `setPassword(String password)`
* `getIdDepartment()` / `setIdDepartment(String idDepartment)`
3. `showInfo`メソッドの中身を、フィールドに直接アクセスする書き方(`this.idEmployee`など)から、1で作ったゲッターを使う書き方に直してください。
4. `Main.java`を見直し、`employee.idEmployee = "10000";`のようにフィールドへ直接代入している箇所を、**すべて**`employee.setIdEmployee("10000");`のようにセッター経由の書き方に直してください。
* 4〜6段階目だけでなく、7〜9段階目(検索機能)で作った箇所も含めて、すべて探して直しましょう。
**<実行例>**
これまでと同じ表示になります(6段階目・9段階目の実行例と見比べて確認してください)。
### 5段階目(EmployeeDataにコンストラクタを作ろう)
**お題**
`EmployeeData`クラスに、コンストラクタを2つ(オーバーロード)追加しましょう。
**<作成フローとポイント>**
1. `EmployeeData.java`に、**引数なしコンストラクタ**を追加してください。
* 中身は空のままでOKです(何もしなくて構いません)。
2. `EmployeeData.java`に、**6つの引数を持つコンストラクタ**を追加してください(1と合わせてオーバーロードになります)。
* 引数:`String idEmployee, String nmEmployee, String knEmployee, String mailAddress, String password, String idDepartment`
* 処理:渡された6つの引数を、直接フィールドに代入するのではなく、対応する**セッターを呼び出して**設定する
* 例:`this.setIdEmployee(idEmployee);`
3. `Main.java`の4段階目で作った「1件分の社員データを作る部分」を、新しく作った6引数コンストラクタを使う形に書き換えてください。
* 例:`EmployeeData employee = new EmployeeData("10000", "山田龍也", "ヤマダタツヤ", "yamada@hoge.jp", "aaa", "10");`
4. 6段階目(5人分)・7〜9段階目(検索機能)で社員データを作っている部分は、これまで通り「引数なしコンストラクタ+セッター」の形のままで構いません。
> **補足**:CSVファイルのようなデータを1項目ずつ読み込んで詰め替えていく場面では、あえて「引数なしコンストラクタ+セッター」の組み合わせが使われることもよくあります(このシステムでも、後日ファイルを扱うミッションでそのような作り方が登場します)。今回作った6引数コンストラクタは、実務でもよく使われる便利な書き方ですが、「どちらが正しい」ということではなく、状況に応じて使い分けるものだと覚えておいてください。
**<実行例>**
これまでと表示内容は変わりません。
### 6段階目(toStringをオーバーライドしよう)
**お題**
`showInfo`メソッドを、`Object`クラスの`toString`メソッドをオーバーライドする形に作り直しましょう。
**<作成フローとポイント>**
1. `EmployeeData.java`の`showInfo`メソッドを、以下のルールで書き換えてください。
* メソッド名を`showInfo`から`toString`に変える
* アクセス修飾子を`public`にする(`showInfo`は修飾子なしでしたが、`toString`は`Object`クラスの`public`なメソッドをオーバーライドするため`public`が**必須**です。つけないとコンパイルエラーになります)
* メソッドの前に`@Override`をつける
* 戻り値を`void`から`String`に変える
* 中で`System.out.println`していた部分はやめて、その文字列を`return`するようにする
2. `Main.java`の中で、`employee.showInfo();`や`employees[i].showInfo();`のように呼び出していた箇所を、**すべて**`System.out.println(employee);`・`System.out.println(employees[i]);`のように、オブジェクトをそのまま渡す形に書き換えてください。
3. 7〜9段階目(検索機能)の中で`showInfo()`を呼んでいた箇所も、同様に書き換えてください。
**<実行例>**
1日目の6段階目・9段階目とまったく同じ表示になることを確認してください。
```
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
```
---
## 【挑戦問題ゾーン】7〜9段階目
ここからは応用問題です。今日習った内容に加えて、自分で少し調べながら進める部分があります。難しく感じても焦らず、じっくり調べながら取り組んでみましょう(経験者の方でも、多少時間がかかる内容です)。
### 7段階目(staticで社員データの件数を数えよう)
**お題**
`EmployeeData`クラスに、これまで何件のインスタンスが作られたかを数える仕組みを追加してください。
**<お題の条件>**
* `private static int empCount = 0;`のような`static`フィールドを追加する
* 2つのコンストラクタ(引数なし・6引数)の**両方**の中で、`empCount`を1増やす処理を追加する
* `static int getEmpCount()`のような`static`メソッドを追加し、`empCount`の値を返せるようにする
* `Main.java`で、6段階目の5人分のインスタンスをすべて生成し終えたあとに、`EmployeeData.getEmpCount()`のように**クラス名から直接**呼び出し、作られた件数を出力する
**<考え方のヒント>**
* `static`がついたフィールドやメソッドは、個々の社員データ(`idEmployee`など)とは違い、「全員で1つを共有する」データです。コンストラクタが呼ばれるたびに増える、という点を意識してみましょう。
**<余裕がある人向け(任意)>**
`static final`をつけると、あとから値を変更できない「定数」を作ることができます。試しに`EmployeeData`クラスに`static final String COMPANY_NAME = "Rhizome";`のような定数を追加し、どこかで出力してみましょう。今回のシステムでは必須ではありませんが、後日のミッションでよく使う書き方です。
**<実行例>**
```
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
現在の社員データ件数:5件
```
### 8段階目(セッターに簡単なチェックを入れてみよう)
**お題**
今の`setNmEmployee`は、どんな値を渡されてもそのまま代入してしまいます。空文字や空白だけの値が渡された場合は代入しないように直してみましょう。
**<お題の条件>**
* `setNmEmployee`の引数`nmEmployee`が、`null`、または前後の空白を取り除いた結果が空文字になる場合、「社員名が空のため設定できませんでした」と表示し、**フィールドへの代入を行わない**
* それ以外の場合は、これまで通りフィールドに代入する
**<考え方のヒント>**
* 文字列の前後の空白を取り除くメソッドと、文字列の長さ(または空文字かどうか)を調べるメソッドを、それぞれ調べてみましょう。
* `null`かどうかを確認する条件を、他の条件より**先に**書かないと、`null`の状態で文字列用のメソッドを呼び出そうとして`NullPointerException`というエラーで止まってしまいます。条件を書く順番に気をつけましょう。
**<実行例>**(`Main.java`で試しに空文字を`setNmEmployee`に渡してみた場合)
```
社員名が空のため設定できませんでした
```
### 9段階目(チェックをもう1つの項目にも広げ、コンストラクタと統一しよう)
**お題**
8段階目で作った「空文字チェック」の考え方を、`idEmployee`(社員ID)にも広げましょう。また、コンストラクタが本当にセッター経由になっているかを見直し、統一します。
**<お題の条件>**
1. `setIdEmployee`メソッドにも、8段階目の`setNmEmployee`と同じような空文字チェックを追加してください。
* `idEmployee`が`null`、または前後の空白を取り除いた結果が空文字になる場合、「社員IDが空のため設定できませんでした」と表示し、フィールドへの代入を行わない
* それ以外の場合は、これまで通りフィールドに代入する
2. 8段階目で作ったチェックは、セッターを直接呼び出したときにしか働きません。5段階目で作った6引数コンストラクタの中身を見直し、**6つのフィールドすべてが、直接代入ではなくセッターを呼び出す形**(`this.setNmEmployee(nmEmployee);`のような形)になっているか確認してください。
* もし直接代入の書き方(`this.nmEmployee = nmEmployee;`など)が残っていた場合は、すべてセッターを呼び出す形に書き換えてください。
**<確認方法>**
`Main.java`で、6引数コンストラクタの`nmEmployee`引数に空文字`""`を渡して`EmployeeData`を生成してみましょう。「社員名が空のため設定できませんでした」と表示され、生成されたインスタンスの`nmEmployee`が設定されない(`null`のままになる)ことを確認してください。同様に、`idEmployee`引数を空文字にした場合も「社員IDが空のため設定できませんでした」と表示されることを確認してください。
**<実行例>**
```
社員名が空のため設定できませんでした
社員IDが空のため設定できませんでした
```
> **ポイント**:「値のチェックはセッターに1回だけ書き、コンストラクタも含めて全員がセッター経由で値を設定する」という作りは、実務でもよく使われる、堅牢なクラス設計の基本パターンです。
---
## まとめ
今日は「コンストラクタ」「アクセス修飾子」「ゲッター・セッター」「メソッドのオーバーライド」「`static`」という道具を使って、1日目に作った土台を大きくバージョンアップしました。特に`EmployeeData`クラスは、フィールドが`private`で守られ、コンストラクタとゲッター・セッターを備え、`toString`で自分の情報を表現できる、完成形にかなり近い姿になりました。
今日書いたコードも消さずに残しておいてください。次回以降のミッションで、継承・多態性・コレクション・例外処理・ファイル入出力などを習いながら、最終的に本物の「社員メンテナンス(ファイルシステム版)」システムに近づけていきます。

View File

@@ -0,0 +1,462 @@
# 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`型でまとめて扱えるようになりました。これは最終的に完成するシステムの土台となる、とても重要な変更です。
今日書いたコードも消さずに残しておいてください。次回以降のミッションで、コレクション・例外処理・ファイル入出力などを習いながら、最終的に本物の「社員メンテナンス(ファイルシステム版)」システムに近づけていきます。

9
em-oop/src/BaseData.java Normal file
View File

@@ -0,0 +1,9 @@
//Day3_4段階目:BaseDataを作り、EmployeeDataに継承させよう
public abstract class BaseData {
public abstract String toString();
public abstract String toCSV();
public abstract String getPrimaryKeyValue();
}

View File

@@ -3,16 +3,25 @@ public class BasicCheck1 {
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
Book book = new Book();
Book book1 = new Book();
book1.title = "タイトル未設定";
book1.author ="著者未設定";
book1.price = 0;
book.title = "吾輩は猫である";
book.author = "夏目漱石";
book.price = 800;
System.out.println(book.title);
System.out.println(book.author);
System.out.println(book.price);
System.out.println(book1.title);
System.out.println(book1.author);
System.out.println(book1.price);
Book book2 = new Book();
book2.title = "銀河鉄道の夜";
book2.author ="宮沢賢治";
book2.price = 500;
System.out.println(book2.title);
System.out.println(book2.author);
System.out.println(book2.price);
}
}

View File

@@ -0,0 +1,21 @@
public class BasicCheck2 {
public static void main(String[] args) {
Student student1 = new Student(1,"鈴木一郎");
Student student2 = new Student(2,"田中花子");
Student student3 = new Student(3,"佐藤次郎");
// student.studentId = 1;
// student.name = "鈴木一郎";
// System.out.print("学籍番号"+student.studentId);
// System.out.print("名前:" + student.name);
student1.printProfile();
student2.printProfile();
student3.printProfile();
System.out.println("現在の生徒数:" + Student.getStudentCount() + "人");
}
}

View File

@@ -0,0 +1,35 @@
public class BasicCheck3 {
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
Item[] items = new Item[3];
items[0] = new Item();
items[1] = new Item();
items[2] = new Item();
items[0].setName("ノート");
items[0].setPrice(120);
items[0].setStock(50);
items[1].setName("消しゴム");
items[1].setPrice(80);
items[1].setStock(30);
items[2].setName("鉛筆");
items[2].setPrice(60);
items[2].setStock(100);
for (int i = 0; i < items.length; i++) {
items[i].sell(5);
}
for (int i = 0; i < items.length; i++) {
System.out.println(items[i]);
}
}
}

View File

@@ -3,4 +3,18 @@ public class Book {
String title;
String author;
int price;
public Book() {
title = "タイトル未設定";
author = "著者未設定";
price = 0;
}
public Book(String title,String author,int price){
this.title = title;
this.author = author;
this.price = price;
}
}

43
em-oop/src/Circle.java Normal file
View File

@@ -0,0 +1,43 @@
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
super("円");
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public void showInfo() {
super.showInfo();
System.out.println("半径:" + this.radius);
}
// //Shapeクラスを継承
//
// //フィールド作成
// private double radius;
//
// //コンストラクタ作成
// public Circle(double radius) {
// super("円");
// this.radius = radius;
// }
//
// //メソッド作成
// public double area() {
// return Math.PI * radius * radius;
// }
//
// public void showInfo() {
// super.showInfo();
// System.out.println("(半径:" + this.radius + ")");
// }
}

View File

@@ -0,0 +1,11 @@
public class Day3Check1 {
public static void main(String[] args) {
Circle circle = new Circle(2.0);
circle.showInfo();
}
}

View File

@@ -0,0 +1,14 @@
public class Day3Check2 {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(3,4);
rectangle.showInfo();
System.out.println("四角形の面積は" + rectangle.area() + "です");
Greeting greeting = new JapaneseGreeting();
System.out.println(greeting.hello());
}
}

View File

@@ -0,0 +1,13 @@
public class Day3Check3 {
public static void main(String[] ages) {
Shape shape1 = new Circle(2);
shape1.showInfo();
Shape shape2 = new Rectangle(3, 4);
shape2.showInfo();
}
}

View File

@@ -0,0 +1,59 @@
public class DepartmentData extends BaseData{
private String idDepartment;
private String nmDepartment;
//gettre・setter
//部署IDのgetter
public String getIdDepartment() {
return idDepartment;
}
//部署IDのsetter
public void setIdDepartment(String idDepartment) {
this.idDepartment = idDepartment;
}
//部署名のgetter
public String getNmDepartment() {
return nmDepartment;
}
//部署名のsetter
public void setNmDepartment(String nmDepartment) {
this.nmDepartment = nmDepartment;
}
//部署名を文字列として返す
@Override
public String toString() {
return this.getIdDepartment() + "\t" + this.getNmDepartment();
}
//CSV形式で返す
@Override
public String toCSV() {
return this.getIdDepartment() + "," + this.getNmDepartment();
}
//部署IDを返す
@Override
public String getPrimaryKeyValue() {
return this.getIdDepartment();
}
//部署名
// @Override
// public String toCSV() {
// return this.getIdDepartment() + "," + getNmDepartment();
// }
//部署ID
// @Override
// public String getPrimaryKeyValue() {
// return this.getIdDepartment();
// }
}

View File

@@ -0,0 +1,153 @@
public class EmployeeData extends BaseData {
private String idEmployee;
private String nmEmployee;
private String knEmployee;
private String mailAddress;
private String password;
private String idDepartment;
//社員データ件数
private static int empCount = 0;
//引数なしコンストラクタ
public EmployeeData() {
empCount++;
}
//6つの引数をもつコンストラクタ
public EmployeeData(
String idEmployee,
String nmEmployee,
String knEmployee,
String mailAddress,
String password,
String idDepartment) {
this.setIdEmployee(idEmployee);
this.setNmEmployee(nmEmployee);
this.setKnEmployee(knEmployee);
this.setMailAddress(mailAddress);
this.setPassword(password);
this.setIdDepartment(idDepartment);
empCount++;
}
// public static int getEmpCount() {
// return empCount;
// }
public String getIdEmployee() {
return idEmployee;
}
public void setIdEmployee(String idEmployee) {
//9段階目(空文字チェック、セッター経由になっているかを見直し)
if (idEmployee == null || idEmployee.trim().isEmpty()) {
System.out.println("社員IDが空のため設定できませんでした");
return;
}
this.idEmployee = idEmployee;
}
//社員名
public String getNmEmployee() {
return nmEmployee;
}
public void setNmEmployee(String nmEmployee) {
//8段階目(空文字や空白だけの値が渡された場合は代入しない)
if (nmEmployee == null || nmEmployee.trim().isEmpty()) {
System.out.println("社員名が空のため設定できませんでした");
return;
}
this.nmEmployee = nmEmployee;
}
//社員名カナ
public String getKnEmployee() {
return knEmployee;
}
public void setKnEmployee(String knEmployee) {
this.knEmployee = knEmployee;
}
//メールアドレス
public String getMailAddress() {
return mailAddress;
}
public void setMailAddress(String mailAddress) {
this.mailAddress = mailAddress;
}
//パスワード
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//部署ID
public String getIdDepartment() {
return idDepartment;
}
public void setIdDepartment(String idDepartment) {
this.idDepartment = idDepartment;
}
//社員データ件数を取得
public static int getEmpCount() {
return empCount;
}
//社員IDが一致するか確認
public boolean isMatchID(String id) {
return this.idEmployee != null && this.idEmployee.equals(id);
}
//社員情報を文字列として返す
@Override
public String toString() {
return this.getIdEmployee() + "\t"
+ this.getNmEmployee() + "\t"
+ this.getKnEmployee() + "\t"
+ this.getMailAddress() + "\t"
+ this.getPassword() + "\t"
+ this.getIdDepartment();
}
//CSV形式で返す
@Override
public String toCSV() {
return this.getIdEmployee() + ","
+ this.getNmEmployee() + ","
+ this.getKnEmployee() + ","
+ this.getMailAddress() + ","
+ this.getPassword() + ","
+ this.getIdDepartment();
}
//社員IDを返す
@Override
public String getPrimaryKeyValue() {
return this.getIdEmployee();
}
}

3
em-oop/src/Greeting.java Normal file
View File

@@ -0,0 +1,3 @@
public interface Greeting {
String hello();
}

43
em-oop/src/Item.java Normal file
View File

@@ -0,0 +1,43 @@
public class Item {
private String name;
private int price;
private int stock;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
void sell(int amount) {
this.setStock(this.getStock() - amount);
}
@Override
public String toString() {
return getName() + "" + getPrice() + "円(残り" + getStock() + "個)";
}
}

View File

@@ -0,0 +1,9 @@
public class JapaneseGreeting implements Greeting {
@Override
public String hello() {
return "こんにちは";
}
}

257
em-oop/src/Main.java Normal file
View File

@@ -0,0 +1,257 @@
public class Main {
public static void main(String[] args) {
//Day3_5段階目:BaseData型で社員を作成
// BaseData employee = new EmployeeData(
// "10000",
// "山田龍也",
// "ヤマダタツヤ",
// "yamada@hoge.jp",
// "aaa",
// "10"
// );
// System.out.println(employee.toCSV());
// System.out.println(employee.getPrimaryKeyValue());
//Day3_5段階目:BaseData型の配列を作成
BaseData[] Employees = new EmployeeData[5];
Employees[0] = new EmployeeData(
"10000",
"山田龍也",
"ヤマダタツヤ",
"yamada@hoge.jp",
"aaa",
"10"
);
Employees[1] = new EmployeeData(
"10001",
"高橋準",
"タカハシジュン",
"jtaka@hoge.jp",
"bbb",
"10"
);
Employees[2] = new EmployeeData(
"10002",
"麻枝史明",
"マエダフミアキ",
"fmak@hoge.jp",
"ccc",
"11"
);
Employees[3] = new EmployeeData(
"10003",
"丸戸章介",
"マルトショウスケ",
"marusyo@hoge.jp",
"ddd",
"12"
);
Employees[4] = new EmployeeData(
"10004",
"三宅武",
"ミヤケタケシ",
"miya@hoge.jp",
"eee",
"13"
);
//社員+部署をまとめる配列
BaseData[] allData = new BaseData[11];
//
for (int i = 0; i < Employees.length; i++) {
allData[i] = Employees[i];
}
//部署データ6件
DepartmentData dept1 = new DepartmentData();
dept1.setIdDepartment("10");
dept1.setNmDepartment("役員");
allData[5] = dept1;
DepartmentData dept2 = new DepartmentData();
dept2.setIdDepartment("11");
dept2.setNmDepartment("人事・総務部");
allData[6] = dept2;
DepartmentData dept3 = new DepartmentData();
dept3.setIdDepartment("12");
dept3.setNmDepartment("経理部");
allData[7] = dept3;
DepartmentData dept4 = new DepartmentData();
dept4.setIdDepartment("13");
dept4.setNmDepartment("システム開発事業部");
allData[8] = dept4;
DepartmentData dept5 = new DepartmentData();
dept5.setIdDepartment("14");
dept5.setNmDepartment("教育事業部");
allData[9] = dept5;
DepartmentData dept6 = new DepartmentData();
dept6.setIdDepartment("15");
dept6.setNmDepartment("営業企画部");
allData[10] = dept6;
//6段階目
//全データ表示
// for (int i = 0; i < allData.length; i++) {
// System.out.println(allData[i]);
// }
//7段階目:instanceofで件数を数える
// int employeeCount = 0;
// int departmentCount = 0;
//
// for(int i = 0; i < allData.length; i++) {
// if(allData[i] instanceof EmployeeData) {
// employeeCount++;
// }
//
// if(allData[i] instanceof DepartmentData) {
// departmentCount++;
// }
// }
//
// System.out.println("社員データ" + employeeCount + "件");
// System.out.println("部署データ" + departmentCount + "件");
//8段階目
//全社員のメールアドレスを表示
// for(int i = 0; i < allData.length; i++) {
// if(allData[i] instanceof EmployeeData) {
// EmployeeData employeeData = (EmployeeData) allData[i];
//
// System.out.println(employeeData.getMailAddress());
// }
// }
//部署名から社員を検索
String targetDepartmentId = null;
for(int i = 0; i < allData.length;i++) {
if(allData[i] instanceof DepartmentData) {
DepartmentData departmentData = (DepartmentData) allData[i];
if(departmentData.getNmDepartment().equals("システム開発事業部")) {
targetDepartmentId = departmentData.getIdDepartment();
}
}
}
for (int i = 0; i < allData.length; i++) {
if(allData[i] instanceof EmployeeData) {
EmployeeData employeeData = (EmployeeData) allData[i];
if(employeeData.getIdDepartment().equals(targetDepartmentId)) {
System.out.println(employeeData.getNmEmployee() + "\t" + employeeData.getMailAddress());
}
}
}
// Employees[0].setIdEmployee("10000");
// Employees[0].setNmEmployee("山田龍也");
// Employees[0].setKnEmployee("ヤマダタツヤ");
// Employees[0].setMailAddress("yamada@hoge.jp");
// Employees[0].setPassword("aaa");
// Employees[0].setIdDepartment("10");
// System.out.println("10000を検索します");
// System.out.println(Employees[0].isMatchID("10000"));
// Employees[1].setIdEmployee("10001");
// Employees[1].setNmEmployee("高橋準");
// Employees[1].setKnEmployee("タカハシジュン");
// Employees[1].setMailAddress("jtaka@hoge.jp");
// Employees[1].setPassword("bbb");
// Employees[1].setIdDepartment("10");
// Employees[2].setIdEmployee("10002");
// Employees[2].setNmEmployee("麻枝史明");
// Employees[2].setKnEmployee("マエダフミアキ");
// Employees[2].setMailAddress("fmak@hoge.jp");
// Employees[2].setPassword("ccc");
// Employees[2].setIdDepartment("11");
// Employees[3].setIdEmployee("10003");
// Employees[3].setNmEmployee("丸戸章介");
// Employees[3].setKnEmployee("マルトショウスケ");
// Employees[3].setMailAddress("marusyo@hoge.jp");
// Employees[3].setPassword("ddd");
// Employees[3].setIdDepartment("12");
// Employees[4].setIdEmployee("10004");
// Employees[4].setNmEmployee("三宅武");
// Employees[4].setKnEmployee("ミヤケタケシ");
// Employees[4].setMailAddress("miya@hoge.jp");
// Employees[4].setPassword("eee");
// Employees[4].setIdDepartment("13");
//5人分の社員情報を表示
// for (int i = 0; i < Employees.length; i++) {
// System.out.println(Employees[i]);
//
// }
//社員データの件数を表示
// System.out.println("現在の社員データ件数:" + EmployeeData.getEmpCount() + "件");
//社員ID検索機能
// Scanner scanner = new Scanner(System.in);
// while(true) {
// System.out.println("検索したい社員IDを入力してください(終了する場合は0)");
// String id = scanner.nextLine();
// if(id.equals("0")) {
// System.out.println("システムを終了します");
// break;
// }
// boolean found = false;
// for (int i = 0; i < Employees.length; i++) {
//
// if(Employees[i].getPrimaryKeyValue().equals(id)) {
// System.out.println(Employees[i]);
// found = true;
// break;
// }
// }
//
// if(found == false) {
// System.out.println("該当する社員が見つかりませんでした");
// }
// }
// Employees[0].showInfo();
//
// System.out.println(Employees.idEmployee);
// System.out.println(Employees.nmEmployee);
// System.out.println(Employees.knEmployee);
// System.out.println(Employees.mailAddress);
// System.out.println(Employees.password);
// System.out.println(Employees.idDepartment);
}
}

20
em-oop/src/Rectangle.java Normal file
View File

@@ -0,0 +1,20 @@
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
super("四角");
this.width = width;
this.height = height;
}
@Override
public double area() {
// TODO 自動生成されたメソッド・スタブ
return width * height;
}
}

41
em-oop/src/Shape.java Normal file
View File

@@ -0,0 +1,41 @@
//
//public abstract class Shape {
//
// private String name;
//
// public Shape(String name) {
// this.name = name;
//
// }
//
// public abstract double area();
// //return 0.0;
//
//
//
// public void showInfo() {
// //System.out.println(this.name + "の面積は" + this.area() + "です");
//
// }
public class Shape {
//フィールド作成
private String name;
//コンストラクタ作成
public Shape(String name) {
this.name = name;
}
//メソッド作成
double area() {
return 0.0;
}
//メソッド作成
void showInfo() {
//出力
System.out.println(this.name + "の面積は" + this.area() + "です");
}
}

30
em-oop/src/Student.java Normal file
View File

@@ -0,0 +1,30 @@
public class Student {
int studentId;
String name;
static int studentCount = 0;
public Student(int studentId, String name) {
this.studentId = studentId;
this.name = name;
studentCount++;
}
public void printProfile() {
System.out.println("学籍番号:" + this.studentId + " 名前:" + this.name);
}
//public static void main(String[] args) {
//Student s = new Student(1,"鈴木一郎");
//s.printProfile();
//}
public static int getStudentCount() {
return studentCount;
}
}