🎯 最終演習

Javaで図書館の貸出を管理してみよう(総合演習)

ここまでの Java レッスンで学んだことを全部つないで、動くものを1つ完成させます。新しい構文は出てきません。クラス・enum・例外・List・for文・三項演算子——すでに練習したものだけで組み立てます。

作るのは図書館の貸出管理です。誰が何を借りているかを持ち、期限を過ぎたものを見つけ、延滞料金を計算して、一覧にまとめます。

Javaらしさがいちばん出るのは「包む」ところです。データをclassに閉じ込め、状態をenumで限定し、失敗を例外で伝える——この3つが揃うと、おかしな状態になりようがないプログラムになります。

このページではコードを実行していません。Javaはコンパイルが必要な言語で、ブラウザの中では走らせられないためです。かわりにお手本と同じコードを書けたかを、その場で判定します(判定はあなたのブラウザの中だけで行われ、どこにも送信されません)。

表示している実行結果は本物です。このページを作るときに、実際のJavaでコンパイルして走らせた出力をそのまま載せています。

完成イメージ

最後まで進むと、このコードが完成します。下の実行結果は、このページを作るときに開発機で実際にコードを走らせて得たものです(このページ上では実行していません)。

貸出できません: 田中さんは既に3冊借りています(上限3冊)

===== 貸出状況(20日時点) =====
統計の入門      田中   期限12日  延滞中  8日超過
小さな家の設計    佐藤   期限25日  貸出中
土を育てる暮らし   鈴木   期限10日  返却済
夜明けの航海記    高橋   期限18日  延滞中  2日超過
デザインの教科書   伊藤   期限30日  貸出中
海の地図       田中   期限28日  貸出中
森の観察記      田中   期限28日  貸出中
----------------------------------------
登録冊数  : 7冊
延滞      : 2冊
延滞料金計: 300円
延滞している方に連絡してください。

STEP 1本1冊をクラスで表す

🎯 このステップのゴール: フィールドをprivateにして、コンストラクタとメソッドで触らせる

まずデータの形をクラスで書きます。本1冊が持つのは「タイトル・借りた人・返却期限・返却済みか」の4つです。

フィールドを全部privateにしているのに注目してください。外からbook.title = "..."と直接書き換えられないようにしています。

なぜ閉じるのか。タイトルや借りた人が、どこからでも書き換えられるとしたら——おかしな値になったとき、犯人を探す場所が全部になります。入口を絞れば、探す場所も絞れます。

finalを付けたフィールドはコンストラクタで一度決めたら二度と変わりません。タイトルと借りた人と期限は変わらないのでfinal、返却済みかどうかだけは変わるので付けていません。「変わるもの」と「変わらないもの」を型で区別しておくと、読む人に意図が伝わります。

値を取り出すのがtitle()のようなメソッドです。JavaではgetTitle()と書く流儀が長く使われてきましたが、最近はtitle()と短く書くことも増えています。どちらでも構いません——チームで揃っていることのほうが大事です。

new Book("統計の入門", "田中", 12, false)で1冊作ります。引数の順番はコンストラクタの通りです。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
タイトル: 統計の入門
借りた人: 田中
返却期限: 12日
返却済み: false

STEP 2状態をenumで表して判定する

🎯 このステップのゴール: 取りうる状態を3つに限定し、三項演算子で振り分ける

貸出の状態は「貸出中・返却済・延滞中」の3つしかありません。これをStringで持つと何でも入ってしまいます

enum Status { ON_LOAN, RETURNED, OVERDUE }——この3つ以外は存在できません。打ち間違えればコンパイルが通らないので、実行する前に間違いが分かります

Javaのenumは、ただの定数の並びではありません。フィールドもコンストラクタもメソッドも持てる、ちゃんとしたクラスです。ここではlabelという日本語名を持たせて、status.label()で取り出せるようにしています。

この設計の効き目は、表示を変えたくなったときに出ます。「貸出中」を「貸出中(返却待ち)」にしたい——直す場所はenumの1行だけです。ifで分岐して文字列を組み立てていたら、書いた場所を全部探すことになります。

判定はstatus(int today)メソッドです。返却済みなら即RETURNED、そうでなければ期限と今日を比べます。

today > dueDay ? Status.OVERDUE : Status.ON_LOAN——三項演算子です。「条件 ? 真のとき : 偽のとき」と読みます。ifで4行書いても同じですが、2択を返すだけならこちらが素直です。

ただし入れ子にしないでください。三項演算子を三項演算子の中に書くと、途端に読めなくなります。3択以上ならifswitchです。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
統計の入門: 延滞中
小さな家の設計: 貸出中
土を育てる暮らし: 返却済
夜明けの航海記: 延滞中
デザインの教科書: 貸出中

STEP 3Listにまとめて延滞を数える

🎯 このステップのゴール: 拡張for文で回し、延滞日数と料金を積み上げる

本をまとめて扱います。使うのはListです。

List<Book> books = new ArrayList<>();——左が型、右が実物という書き方をしています。

なぜ左をArrayListにしないのか。Listは「順番に並ぶもの」という約束だけを表すインターフェースで、ArrayListはその実現方法の1つです。左をListにしておけば、あとでLinkedListに変えたくなっても、この1行を直すだけで済みます

<Book>ジェネリクスです。「このListにはBookしか入らない」と宣言しています。付けないと何でも入ってしまい、取り出すたびに型を変換する羽目になります。

回すのはfor (Book book : books)——拡張for文です。添字を書かないので、範囲を1つ間違えて落ちる、という事故が起きません。全件を順に見るだけなら、こちらを選んでください。

数え上げの型は決まっています。先に入れ物を0で用意し、条件に合ったときだけ足すoverdueCounttotalFeeを両方このやり方で積み上げています。

料金はdays * FEE_PER_DAYです。30を直接書かず定数にしています——料金は必ず変わるものだからです。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
統計の入門 は 8日超過
夜明けの航海記 は 2日超過
延滞: 2冊 / 料金計: 300円

STEP 4独自例外で「借りられない」を伝える

🎯 このステップのゴール: Exceptionを継承した型を作り、投げて受け止める

「1人3冊まで」という決まりを入れます。4冊目を借りようとしたら、断らなければなりません

ここでfalseを返すのではなく、例外を投げます。理由があります——falseには理由が書けないからです。「上限だから」なのか「そんな本は無いから」なのか、呼び出した側は区別できません。

class LoanException extends Exceptionこの図書館専用の例外を作ります。Exceptionを継承するだけで、あとはメッセージを親に渡すコンストラクタを1つ書けば完成です。

なぜ既存のExceptionをそのまま使わないのか。専用の型を作ると、catch (LoanException e)で「貸出の失敗だけ」を捕まえられます。他の失敗まで一緒に握りつぶしてしまうことがありません。

投げるのがthrow new LoanException("...")メソッドの宣言にthrows LoanExceptionと書く必要があります——Javaは「このメソッドは失敗しうる」を型に書かせる言語です。

これを検査例外と呼びます。呼ぶ側はtry-catchで受けるか、自分もthrowsを付けて上に投げるか、どちらかを必ず選ばされます。書き忘れができません。

受けるのがcatch (LoanException e)で、e.getMessage()投げるときに渡した文章が入っています。だから「なぜ断られたか」がそのまま利用者に見せられます

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
3冊まで借りられました: 3冊
貸出できません: 田中さんは既に3冊借りています(上限3冊)

STEP 5一覧を表の形に整える

🎯 このステップのゴール: printfで桁を揃え、カンマ区切りの金額を出す

数字が並んでいるだけでは読めません。表の形に整えます

使うのはSystem.out.printfです。%-10sは「左揃えで10文字ぶんの文字列」、%2dは「右揃えで2桁の整数」。文字列は左、数字は右に揃えると読みやすくなります。

金額には%,dを使います。カンマ区切りで1,200のように出ます。手で3桁ごとに区切る処理を書く必要はありません——標準の書式指定でできます。

改行に%nを使っているのに注目してください。\nでも改行できますが、%nそのパソコンに合った改行文字を出します。Windowsでも同じように見えるので、printfの中では%nを使うのが習慣です。

延滞日数の欄は三項演算子で出し分けますdays > 0 ? String.format(" %d日超過", days) : ""——遅れていなければ空文字にして、何も出しません。

ここでString.formatを使っているのが要点です。printfは画面に出すだけですが、String.format同じ書式で文字列を作って返します。あとで組み立てたいときは、こちらです。

日本語は文字の幅が英数字の2倍で表示されるので、%-10sでも見た目は完全には揃いません。大きくは整うので実用になりますが、厳密に揃えたいならHTMLの表を使うのが本筋です。

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
統計の入門      田中   期限12日  延滞中  8日超過
小さな家の設計    佐藤   期限25日  貸出中
土を育てる暮らし   鈴木   期限10日  返却済
夜明けの航海記    高橋   期限18日  延滞中  2日超過
デザインの教科書   伊藤   期限30日  貸出中
延滞料金計: 300円

STEP 6ひとつのメソッドにまとめて仕上げる

🎯 このステップのゴール: report にまとめ、貸出の失敗もあわせて動かす

最後の仕上げです。ここまでの部品をreportという1つのメソッドにまとめます

並べる順番に意味を持たせます。まず1冊ずつの状況、次に区切り線、最後に全体のまとめ——細かいものから大きいものへという流れです。

mainでは貸出の失敗も一緒に動かします。3冊借りている田中さんが4冊目を借りようとして断られる——ステップ4で作った例外が、実際の流れの中で働くところが見られます。

tryの中に3回のcheckOutをまとめて書いているのが要点です。1回ごとにtry-catchを書くこともできますが、「どれかが失敗したら、そこで打ち切る」という意図なら、まとめて囲むほうが自然に表せます。

static finalの定数を3つ並べました。TODAYFEE_PER_DAYMAX_LOANS——どれも後から変わる値です。staticは「インスタンスごとではなくクラスに1つ」、finalは「書き換えない」という意味です。

完成です。TODAYを10に変えてみてください。延滞が0冊になり、締めの言葉が「延滞はありません。」に変わります。1つの数字を変えるだけで、判定も料金も表示も全部連動する——定数に切り出しておいた効果が確かめられます。

次に進むなら、本のリストをファイルから読むところです。reportList<Book>を受け取るだけなので、作り方が変わっても、そのまま使えます

📖 お手本コード
✍️ あなたのコード
コードを書いたら「答え合わせ」を押してください
実行結果(開発機で実行したもの)
貸出できません: 田中さんは既に3冊借りています(上限3冊)

===== 貸出状況(20日時点) =====
統計の入門      田中   期限12日  延滞中  8日超過
小さな家の設計    佐藤   期限25日  貸出中
土を育てる暮らし   鈴木   期限10日  返却済
夜明けの航海記    高橋   期限18日  延滞中  2日超過
デザインの教科書   伊藤   期限30日  貸出中
海の地図       田中   期限28日  貸出中
森の観察記      田中   期限28日  貸出中
----------------------------------------
登録冊数  : 7冊
延滞      : 2冊
延滞料金計: 300円
延滞している方に連絡してください。

よくある質問

このページではコードを実行しないのですか?

していません。Javaはコンパイルが必要な言語で、ブラウザの中では走らせられないためです。このページではお手本と同じコードを書けたかどうかを判定する形にしています(判定はあなたのブラウザの中だけで行われ、コードはどこにも送信されません)。

ただし表示している実行結果は本物です。このページを作るときに実際のJavaでコンパイルして走らせた出力を、そのまま載せています。このコードをMain.javaという名前で保存してjava Main.javaと実行すれば、まったく同じ結果が出ます。

public class が1つしか書けないのはなぜですか

ファイル名とクラス名を一致させる決まりがあるからです。public class MainMain.javaに書かなければなりません。1ファイルにpublicなクラスが2つあると、どちらの名前をファイル名にすればよいか決まりません

この演習ではBookStatuspublicを付けていないので、同じファイルに置けます。

実際の開発では1クラス1ファイルに分けるのが普通です。ここでは手元でそのまま試せることを優先して、1つにまとめてあります。

例外を投げるのと false を返すのは、どう使い分けますか

「よくあること」なら戻り値、「起きてはいけないこと」なら例外というのが目安です。

たとえば「検索して見つからなかった」は普通に起こるので、nullや空のリストを返すほうが素直です。

一方この演習の「上限を超えた」は断る理由を伝える必要があるので、例外が向いています。falseでは「なぜ断られたか」が呼び出し側に届きません

迷ったら「呼び出した側は、この失敗をどう扱いたいか」を考えてみてください。

%n と \\n は何が違いますか

改行文字が環境によって変わるかどうかです。

\\nは必ず「LF」という1文字を出します。%nそのパソコンの標準的な改行を出すので、Windowsでは「CRLF」の2文字になります。

画面に出すだけならどちらでも同じに見えます。ただしファイルに書き出すときは差が出ます——Windowsのメモ帳で開いたときに改行されない、という古典的な問題です。printfの中では%nを使う習慣にしておくと無難です。

🚩 次はこのカテゴリへ

KotlinはJavaと同じ土台の上で動く言語です。この演習で書いたBookクラスは、Kotlinならdata classの1行でほぼ同じものが書けます。Javaを知っているほど、Kotlinの短さの意味が分かります。Kotlinカテゴリの最終演習で、その違いを確かめてみてください。

Kotlinのレッスンへ進む
広告スペース(バナー)
広告スペース(記事内)