26 KiB
CUI_EM_OOP ミッション課題【2日目】コンストラクタ・カプセル化・static
このミッションについて
このミッションは、最終的に 「社員メンテナンス(ファイルシステム版)」(CUI_EM_OOP) を完成させることを目標にした、複数回に分かれたミッション課題の2日目です。
前回(1日目)作成したプロジェクトのファイルを、そのまま引き続き使います。 同じプロジェクトに追記・修正を加えていってください(Book、Student、Item、EmployeeData、Main、BasicCheck1〜3など)。
今日は、1日目に作った「まだ簡易版」のクラスたちを、今日習う道具を使って、完成形にぐっと近づけていきます。それでも、まだ以下の内容は習っていないため使いません。今後のミッションでさらにバージョンアップしていきます。
- 継承・多態性(
extends、抽象クラス) - コレクション(
ArrayListなど) - 例外処理
- ファイルの読み書き
今日の講義内容
- コンストラクタ
- コンストラクタのオーバーロード
- メソッドのオーバーライド(
ObjectクラスのtoStringなど) - アクセス修飾子
- ゲッター・セッター
static、静的メンバ
基本の書き方(文法メモ)
今日はじめて出てくる書き方をまとめておきます。各段階で迷ったときは、ここに戻って確認してください。
コンストラクタ
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つだけ共有されるものになります。
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段階目から順番に取り組んでください。むずかしそうに見えても、飛ばさないでください。
- 各段階の「お題」をよく読んで、「何を作るのか」を最初に理解してから手を動かしましょう。
- コードが書けたら、必ず実行して**「実行例」と同じ結果になるか**確認しましょう。
- わからない言葉が出てきたら、まず自分で調べてみましょう。調べてもわからないときは、講師に質問してください。
- コピー&ペーストではなく、自分の手で入力して理解しましょう。
提出物
- 今日修正・作成した
.javaファイル一式
【基礎確認ゾーン】1〜3段階目
このゾーンは、CUI_EM_OOPとは直接関係のない練習問題です。1日目に作ったBook・Item・Studentクラスを、今日習った道具でバージョンアップしながら、基本がしっかり身についているかを確認しましょう。
1段階目(Bookクラスにコンストラクタを作ろう)
お題
Bookクラスに、コンストラクタを2つ(オーバーロード)追加してください。コンストラクタは2つともpublicで宣言してください。
<作成フローとポイント>
Book.javaに、以下の引数なしコンストラクタを追加してください。titleに"タイトル未設定"、authorに"著者未設定"、priceに0を代入する
Book.javaに、以下の3つの引数を持つコンストラクタを追加してください(1と合わせて2つのコンストラクタになります=コンストラクタのオーバーロード)。- 引数:
String title, String author, int price - 処理:渡された3つの引数を、それぞれ対応するフィールドに代入する
- 引数:
BasicCheck1.javaを修正し、mainメソッドの中で以下を行ってください。- 引数なしコンストラクタを使って
book1を作成し、そのままtitle・author・priceを出力する - 3つの引数を持つコンストラクタを使って、
book2を"銀河鉄道の夜"・"宮沢賢治"・500で作成し、同様に出力する
- 引数なしコンストラクタを使って
<実行例>
タイトル未設定
著者未設定
0
銀河鉄道の夜
宮沢賢治
500
2段階目(Itemクラスをカプセル化し、toStringをオーバーライドしよう)
お題
Itemクラスのフィールドをprivateにして、ゲッター・セッターを作成しましょう。また、Objectクラスから受け継いでいるtoStringメソッドをオーバーライドしてみましょう。
<作成フローとポイント>
Item.javaの3つのフィールド(name、price、stock)に、すべてprivateをつけてください。- 3つのフィールドそれぞれに、ゲッター・セッターを作成してください。
getName()/setName(String name)getPrice()/setPrice(int price)getStock()/setStock(int stock)
sellメソッドの中身を、フィールドに直接アクセスする書き方(this.stock = this.stock - amount;)から、作成したゲッター・セッターを使う書き方に直してください。- 例:
this.setStock(this.getStock() - amount);
- 例:
Item.javaに、以下のメソッドを追加してください。- アクセス修飾子:
public(必須です。理由は冒頭の「基本の書き方(文法メモ)」を参照してください) @Overrideをつける- メソッド名:
toString - 引数:なし
- 戻り値:
String - 中身:
getName() + " " + getPrice() + "円 (残り" + getStock() + "個)"のような文字列をreturnする
- アクセス修飾子:
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で作ってみましょう。
<作成フローとポイント>
Student.javaに、以下のコンストラクタを追加してください。- 引数:
int studentId, String name - 処理:渡された引数を、それぞれ対応するフィールドに代入する
- 引数:
Student.javaに、以下のstaticフィールドを追加してください。static int studentCount = 0;(生徒が何人作られたかを数えるための、クラス全体で共有するフィールド)
- 2で作ったコンストラクタの中で、
studentCountを1増やす処理を追加してください(studentCount++;)。 Student.javaに、以下のstaticメソッドを追加してください。- メソッド名:
getStudentCount - 引数:なし
- 戻り値:
int - 中身:
studentCountの値をreturnする
- メソッド名:
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にして、ゲッター・セッターを作成しましょう。
<作成フローとポイント>
EmployeeData.javaの6つのフィールドすべてに、privateをつけてください。- 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)
showInfoメソッドの中身を、フィールドに直接アクセスする書き方(this.idEmployeeなど)から、1で作ったゲッターを使う書き方に直してください。Main.javaを見直し、employee.idEmployee = "10000";のようにフィールドへ直接代入している箇所を、すべてemployee.setIdEmployee("10000");のようにセッター経由の書き方に直してください。- 4〜6段階目だけでなく、7〜9段階目(検索機能)で作った箇所も含めて、すべて探して直しましょう。
<実行例>
これまでと同じ表示になります(6段階目・9段階目の実行例と見比べて確認してください)。
5段階目(EmployeeDataにコンストラクタを作ろう)
お題
EmployeeDataクラスに、コンストラクタを2つ(オーバーロード)追加しましょう。
<作成フローとポイント>
EmployeeData.javaに、引数なしコンストラクタを追加してください。- 中身は空のままでOKです(何もしなくて構いません)。
EmployeeData.javaに、6つの引数を持つコンストラクタを追加してください(1と合わせてオーバーロードになります)。- 引数:
String idEmployee, String nmEmployee, String knEmployee, String mailAddress, String password, String idDepartment - 処理:渡された6つの引数を、直接フィールドに代入するのではなく、対応するセッターを呼び出して設定する
- 例:
this.setIdEmployee(idEmployee);
- 例:
- 引数:
Main.javaの4段階目で作った「1件分の社員データを作る部分」を、新しく作った6引数コンストラクタを使う形に書き換えてください。- 例:
EmployeeData employee = new EmployeeData("10000", "山田龍也", "ヤマダタツヤ", "yamada@hoge.jp", "aaa", "10");
- 例:
- 6段階目(5人分)・7〜9段階目(検索機能)で社員データを作っている部分は、これまで通り「引数なしコンストラクタ+セッター」の形のままで構いません。
補足:CSVファイルのようなデータを1項目ずつ読み込んで詰め替えていく場面では、あえて「引数なしコンストラクタ+セッター」の組み合わせが使われることもよくあります(このシステムでも、後日ファイルを扱うミッションでそのような作り方が登場します)。今回作った6引数コンストラクタは、実務でもよく使われる便利な書き方ですが、「どちらが正しい」ということではなく、状況に応じて使い分けるものだと覚えておいてください。
<実行例>
これまでと表示内容は変わりません。
6段階目(toStringをオーバーライドしよう)
お題
showInfoメソッドを、ObjectクラスのtoStringメソッドをオーバーライドする形に作り直しましょう。
<作成フローとポイント>
EmployeeData.javaのshowInfoメソッドを、以下のルールで書き換えてください。- メソッド名を
showInfoからtoStringに変える - アクセス修飾子を
publicにする(showInfoは修飾子なしでしたが、toStringはObjectクラスのpublicなメソッドをオーバーライドするためpublicが必須です。つけないとコンパイルエラーになります) - メソッドの前に
@Overrideをつける - 戻り値を
voidからStringに変える - 中で
System.out.printlnしていた部分はやめて、その文字列をreturnするようにする
- メソッド名を
Main.javaの中で、employee.showInfo();やemployees[i].showInfo();のように呼び出していた箇所を、すべてSystem.out.println(employee);・System.out.println(employees[i]);のように、オブジェクトをそのまま渡す形に書き換えてください。- 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)にも広げましょう。また、コンストラクタが本当にセッター経由になっているかを見直し、統一します。
<お題の条件>
setIdEmployeeメソッドにも、8段階目のsetNmEmployeeと同じような空文字チェックを追加してください。idEmployeeがnull、または前後の空白を取り除いた結果が空文字になる場合、「社員IDが空のため設定できませんでした」と表示し、フィールドへの代入を行わない- それ以外の場合は、これまで通りフィールドに代入する
- 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で自分の情報を表現できる、完成形にかなり近い姿になりました。
今日書いたコードも消さずに残しておいてください。次回以降のミッションで、継承・多態性・コレクション・例外処理・ファイル入出力などを習いながら、最終的に本物の「社員メンテナンス(ファイルシステム版)」システムに近づけていきます。