Files
em-oop/Day02_コンストラクタ・カプセル化・static.md
2026-10-15 13:55:29 +09:00

389 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`で自分の情報を表現できる、完成形にかなり近い姿になりました。
今日書いたコードも消さずに残しておいてください。次回以降のミッションで、継承・多態性・コレクション・例外処理・ファイル入出力などを習いながら、最終的に本物の「社員メンテナンス(ファイルシステム版)」システムに近づけていきます。