銀行口座クラス
呼び終わった瞬間に、途中経過が消える
これまで書いてきた static メソッドは、呼ばれるたびに白紙から始まります。中で作ったローカル変数は、return した瞬間にすべて捨てられます。
同じ引数を渡せば必ず同じ答えが返る、というのはとても扱いやすい性質です。ただ、残高のように「前回の続き」を持つものは、この形では表せません。入金してから引き出す、という 2 つの操作をつなぐには、操作と操作のあいだ、どこかが値を覚えていてくれる必要があります。
覚えていられるのは、new で作った実体の中のフィールドです。カプセル化の回で書いた Player は、体力を private のフィールドで持ち、damage で減らして getHp で読み出すクラスでした。
Java
Player p = new Player();
p.damage(30);
p.damage(30);
System.out.println(p.getHp()); // 40damage を 2 回呼ぶと、2 回ぶんちゃんと減ります。1 回目の結果が p の中に残っているからです。呼び出しをまたいで値が積み上がっていく ─ これが「状態を持つオブジェクト」です。
変数に入っているのは、実体ではなく矢印
状態を持つものを扱うときに、必ず一度は驚くのがここです。配列で先に見ておきましょう。
Java
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 99;
System.out.println(a[0]); // 99b を書き換えたのに a が変わりました。int[] b = a; は中身を写したのではなく、a と同じ配列を指す矢印をもう 1 本引いただけだからです。
自分で作ったクラスもまったく同じです。
Java
Player q = p;
q.damage(10);
System.out.println(p.getHp()); // 触っていない p も減るPlayer q = p; と書いても、プレイヤーは 2 人になりません。別の実体がほしいなら、new をもう一度呼ぶしかありません。
intやbooleanは値そのものが変数に入り、配列やクラスから作った実体は矢印が入る。この 2 種類の違いは Java を書くかぎりずっと付いて回ります。「変数を代入したのに、片方を触ったらもう片方も変わった」と感じたら、まずここを疑ってください。
やってみよう
右のエディタで bankFlow(int initial, int deposit, int withdraw) を完成させます。口座を 1 つ作り、入金し、引き出し、最後の残高を返す、という一連の流れです。
Solution の中に入れ子で Account クラスを書きます。残高のフィールドは private にして、入金と引き出しはそれぞれメソッドにしてください。おかしな金額を弾く判定は、呼び出し側ではなくそのメソッドの中に置きます。今回は条件に合わないとき、例外を投げずに黙って何もしない仕様です。どの金額を受け付けるかは課題の要件に書いてあるとおりに揃えてください。
緑になったら、口座を 2 つ作って別々に入金し、片方の残高だけが動くことを確かめてみてください。ここで new を 1 回しか呼ばずに変数だけ増やすと、さっきの矢印の話が目の前で再現します。
要件
- Solution の中に static class Account を定義し、private int balance フィールドと public Account(int initial) コンストラクタを持たせる
- Account に deposit(int amount) / withdraw(int amount) / getBalance() を実装し、deposit は amount > 0 のときだけ、withdraw は amount > 0 かつ amount <= balance のときだけ残高を更新する
- Solution.bankFlow(int initial, int deposit, int withdraw) の中で new Account(initial) → deposit → withdraw → getBalance() の順に呼んで最終残高を int で返す
入出力例
bankFlow(1000, 500, 300) → 1200
bankFlow(1000, -100, 200) → 800
bankFlow(100, 0, 200) → 100
bankFlow(0, 500, 500) → 0
bankFlow(500, -50, 1000) → 500
bankFlow(200, 300, 500) → 0