Mr.Hack | ||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ◆目次◆
ユーザーに入力を促すメッセージ『預金額を入力して下さい(預金しない場合は0を入力): 』を表示した後、ユーザーから数値を得たいですが、どうすれば良かったのでしたっけ?どうすれば、ユーザーから数値を得ることが出来ますか?ConvertToCelsiusでは何を使いました? そうです。BufferedReaderクラスを使いましたね。 ┌──────────────────────────────────┐ は、皆さんにはおなじみですね。いったんbufferを得ることが出来れば、後はEnterキーを押すまでの文字列は、buffer.readLine()で取り出せます。 ┌──────────────────────────────────┐ とdouble型のdepositAmountを得ます。ここで、depositAmountの値によってプログラムを分岐します。もしdepositAmountがゼロの場合は、宿題で、『ユーザーがなにも預金しない場合は0を入力させる』と与えられているので、BankAccount#deposit()を呼び出さないようにしないといけませんね。もし、ゼロをdeposit()に渡したら、IllegalArgumentExceptionが発生します(API参照)。例外が発生すると、ゼロとマイナスで発生するExceptionクラスを分けていませんから(両方ともIllegalArgumentExceptionが発生)、それが発生した場合、どれがゼロのIllegalArgumentExceptionか、どれがマイナスのIllegalArgumentExceptionか分かりません。つまり、マイナスの場合だけ、『不正な入力です。数値を入力して下さい :』とメッセージを表示することができません。そのため、deposit()メソッドを呼び出すまえに、if文でdepositAmountが0か否かのチェックが必要です。以下が預金のロジック部分です。 ┌──────────────────────────────────┐ ここのプログラムのミソは、例外発生をうまく利用していることです。例外が発生しない場合だけ、breakキーワードで、whileループを抜ける仕組みになっています。ちなみに、while
(true)という部分は、無限ループといいます。 例外発生について言いますと、まず最初に、BufferedReader#readLine()がIOExceptionを発生する可能性があります。もし発生したら、try/catch構文でキャッチしますが、もう一度、そのキャッチしたeを外側のtry/catchでキャッチして、『不正な処理を行ったため、プログラムを終了します』と表示させます。これは、後で説明します。 二番目に例外が発生する可能性は、Double#parseDouble()です。これは、ユーザーがnumeric character(0-9からなる文字)を入力しなかった場合にNumberFormatExceptionを発生します。そのときは、不適切な値なので、『不正な入力です。数値を入力して下さい : 』と表示して、もう一度whileループを実行します。 そして、最後に例外を発生する可能性があるのは、BankAccount#deposit()です。いまは、BankAccountクラスは使わないとしてあるので、コメントしてありますが、マイナス・ゼロの値をdeposit()に渡すと、IllegalArgumentExceptionが発生します。そのときは、発生した例外メッセージと『もう一度入力して下さい : 』を表示し、もう一度whileループを実行します。 皆さんは、もうすでに、例外処理をちゃんと勉強しましたので、これぐらいのtry/catch構文ではびくともしませんね。簡単に感じるでしょう?ただ、どこのメソッドがどの例外を発生しているかが分からないと難しく感じるかもしれません(それ故、自分でAPIを作成するときは必ずRuntimeExceptionも含め、@exceptionタグを使って説明するのでしたね)。 僕は、超ビギナープログラマだった頃、プログラムを書いているときに一番苦手に感じたのが、try/catch構文でした。いまいち仕組みも分からず、何でわざわざスローするのかさえ、分かりませんでした。しかしながら、いったんこのtry/catch構文の意味わかると、とても便利なツールになります。皆さんは、是非例外処理のプロフェッショナルになってくださいね。例外処理で分からなくなったらこのメルマガのバックナンバーをもう一度読んでみて下さい。そして、それでも分からないときは、掲示板から僕に質問して下さい。 ■引き出しの問い合わせ(引き出しなしは0を得る)・残高表示 これは、考え方は、預金の問い合わせと全く同じなので省略します。
さて、預金・引き出しが終わった後、引き続き預金・引き出しを行うか、または、プログラムを終了するかをユーザーは決めることが出来るようにしましょう。 ┌──────────────────────────────────┐
では、char型の値はどうやって得ればいいでしょうか? buffer.readLine()でString型の値を得ましたね。Java SDK API仕様にいってみてください。char型を得るにはどのメソッドを使用すればいいでしょうか? お、メソッドの概要の一番最初にありました。返却型がcharであるのは、charAt()メソッドですね。APIを調べるのはここだけで終わってはいけませんよ。charAt()のリンクを必ずクリックして下さいね。もしかしたら、例外を発生するかもしれませんからね。発生するとしたらどんな例外ですか?引数としてint indexを渡すので、適切なインデックスを渡さないと、プログラマのエラーで発生するRuntimeな例外が発生しそうだ、と推測するのですよ。インデックスの値によって発生する・しないの例外は実行時の例外です。では、クリックして下さい。 ほら、IndexOutOfBoundsExceptionでしたね。JavaはC/C++とちがって、配列にアクセスする場合(配列のインデックスは0から配列のサイズ-1)は、マイナスや配列のサイズと同じ値のインデックス以上を渡すと、IndexOutOfBoundsExceptionというランタイム(実行時)の例外が発生します。 ランタイムの例外といわれても、もう怖くないですね。そうRutimeExceptionを継承しているクラスですね。ですから、実行時に例外が発生するのです。これは、checkでしたっけ?uncheckでしたっけ?(分からない人はバックナンバー参照かメルマガの一番下を参照(2))。 String#charAt()メソッドを見ると、 ┌──────────────────────────────────┐ とあります。つまり、 ┌──────────────────────────────────┐ とすると、'M'という値が、characterに格納されます。『\n』は文字列の終わりを表すエスケープシーケンスで、index 6までがmyStringです。このcharAt()メソッドを利用して、 ┌──────────────────────────────────┐ try { └──────────────────────────────────┘ と出来そうです。if文の中のboolean型のisContinuedは外側のwhileループを続けるか続けないかのトグル(続けるか続けないかのスイッチ)なので、falseを代入して、次の外側のwhileループの式評価に備えます(whileループの式はfalseと評価されるので次回は外側のwhileループを抜け出すことが出来る)。 設問では、『 h)qかQをタイプした場合のみ、プログラムを抜ける。』となっているので、qとQをタイプした場合のみbreakするようにすればいいですね。それ以外のキー入力は全てもう一度、預金・引き出しを続けるようにする場合は、 ┌──────────────────────────────────┐ とします。僕は、エンターキーを押した時だけ、もう一度預金・引き出しを続けられるようにし、'q'、'Q'以外のどんな文字も『不正な入力です。QまたはEnterキーを押して下さい : 』としてユーザーにもう一度入力を促すようにしました。それが以下のパートです。 ┌──────────────────────────────────┐ ところで、Enterキーを押した場合は、buffer.readLine()は空文字列ですのでcharAt(0)を使うと、IndexOutOfBoundsExceptionがスローされます。その仕組みはどうなっているかといいますと、以下の例を考えてみて下さい。 ┌──────────────────────────────────┐ 空文字列の場合、インデックス0にはアクセス出来ませんね。インデックス0はインデックスの範囲外(out of bounds)なのです。つまり、空文字列の時にcharAt(0)を使うと、IndexOutOfBoundsExceptionが発生します。 今回はその例外が発生した場合だけ、引き続き預金・引き出しが出来るようにしました。もちろん、エンターキー以外でも預金・引き出しが出来るようにしてもいいですよ。 ┌──────────────────────────────────┐ いいですよ、それも可能です。char型でチェックするのではなく、String型でチェックしてみましょうか?これは、今週の宿題で挑戦してみましょう。 ■IOExceptionのキャッチ さて、先ほど預金をするロジックのところで、BufferedReader#readLine()がIOExceptionを発生する可能性があります、と書きました。その場合の対処方法ですが、buffer.readLine()でIOExceptionの例外が発生したら、リカバリーは難しいと思います。ですから、それが発生したら、whileループを途中で中断して、『するべきこと』をして、直ちにプログラムを終了するのが賢明な選択だと思います。『するべきこと』とは、プログラムを終了する前に、bufferストリームを閉じてあげることですね。それには、try/catch/finally構文を使うと以前述べました。つまり、 ┌──────────────────────────────────┐ finallyを使えば、buffer.close()を必ず実行できましたね。普段は、finallyはcatch節の後に実行されますが、catch節が、return、continue、breakキーワードを含んでいるときは、そのキーワードの前に実行されます。つまり、returnでmain()メソッドを終了する前に、上記の部分で、buffer.close()が必ず実行されることになります。 ところで、各預金・引き出し・プログラム続行のところでIOExceptionをtry節でキャッチするたびにfinally節を使うと合計で3回使わないといけなくなりますね。例えば、預金のロジックで、 ┌──────────────────────────────────┐ しかし、IOExceptionキャッチ節でreturnする代わりにそのまま、eをスローしてあげます。 ┌──────────────────────────────────┐ そのスローされた、eはどこでキャッチするのですか?キャッチするtry/catch/finally構文を外側のwhileループにもうけてあげて ┌──────────────────────────────────┐ とします。こうすれば、finally節の記述は一回ですみますね。ところで、内部のwhileループで ┌──────────────────────────────────┐ catch (IOExcetpion e) { └──────────────────────────────────┘ としましたが、実際には、catch節は記述しなくても大丈夫です。というのは、外側のtryでIOExceptionをキャッチしてくれるからです。上記のcatch節があるとbuffer.readlLine()でキャッチしたIOExceptionを、もう一度スローして外側のtry/catch構文でキャッチすることになるので、余分な記述ですね。しかしながら、分かりやすいように今回はこうやって記述しました。読みやすさを考えると余分でも記述した方がいいかもしれません。
最後に、今回BankAccountクラスを使用しなかった理由を見てみましょう。それの最大の理由は、コンソールベースのユーザーインターフェイスに集中してもらうための練習です。今の段階では、クラスは、BankAccountクラスとそれのユーザインターフェイスであるBankUserInterfaceクラスしかありません。BankAccountクラスも難しくないので、すぐに完成させることが出来るでしょう。しかし、もしこれが、データ構造を実装してlinkedlistを使ったり、データベースに接続していたらどうしましょう?それらのクラスが完成するまで、BankUserInterfaceクラスは実行出来ませんね。実行できないということは実際にユーザーインターフェイスがちゃんと動いているか確認も出来ないということです。それでは、こまりますね。自分一人でこのプロジェクトを担当している場合は、全てのロジック部分のクラスを完成してはじめて、ユーザーインターフェイスのチェックが出来ます。このプロジェクトに他の人が関わっていれば、他の人のクラスが完成してはじめて、ユーザーインターフェイスを担当しているあなたは、BankUserInterfaceクラスをチェックできるのです。今回は、バックグランドのロジックに頼らずに、ユーザーインターフェイスだけでコンパイル実行出来るようにしました。 ところで、そもそもユーザーインターフェイス(UI)とはどういう役目なのでしょうか?ユーザーから値をもらって、その値を他のクラスのメソッドに渡してあげることも一つの役割です。それよりも重要なのは、ユーザーがしたいことを的確に実行できるようにしないといけません。たとえば、預金したいのであれば、預金するロジックを呼び出してあげます。途中で預金するのをやめたくなった場合は、やめられるような方法(例えば、qを押したら預金終了のような方法)を選択肢として示さないといけません。その選択肢をユーザーが望むように提示してあげるのがユーザーインターフェイスの役目なのです。ユーザーから得た値を加工・処理するのはバックグランドのロジック部が担ってくれます。UIはそのロジック部が要求しているフォーマットを用意してあげてさえいれば、十分なのです。例えば、BankAccount#deposit()がdouble型の正の整数を引数として要求していれば、それを用意してあげればいいのです。そこでユーザーインターフェイスの役割は終わりです。 あとは、BankAccountクラスが準備できれば、コメントをとって今までテスト用に表示してた数行をとれば完了です。たとえば、預金ロジックのところでは、 ┌──────────────────────────────────┐ コメントしてある行のコメント、//をとって、Systemから始まる行3つと、最後のbalance = (int) depositAmountを削除してあげれば、完成するはずです。もちろん、deposit()メソッドを使う前に、BankAccountのインスタンスbankAccountObjectを初期化してあげないといけませんね。 この件に関しては、掲示板でも扱っているので、参考にして下さい。とても熱心な読者の方から質問をいただきましたので、そこにも今回のヒントがあると思います。その方のように、メルマガ発行までに実際取り組むということは素晴らしいとおもいます。実際に自分で考えて見ないと、分からないところが分からないままだと思います。ぜひ、分からないところを分かって、自分で調べるなり、掲示板で質問するなりしてみて下さい。皆さんの学力がアップすること間違いなしだと思います。 それでは、今週の宿題にいってみましょう。 ■Question 10 1.BankAccountクラスを実装して、実際に預金・引き出し・残高が正常に作動しているかどうか確認しなさい。 2.BankUserInterface.javaを以下のように変更しなさい。 a) 一連の預金・引き出し・参照というプログラムの流れから、 1.定義:boolean isContinued(); ヒント: 今回は、前回のユーザーインターフェイスの発展系です。ユーザーインターフェイス作成チームに入ったあなたは、預金・引き出し・残高参照を一連にするプログラムを書きました。しかしながら、毎回、預金・引き出し・残高を参照するのは、どうも不便ではないかと、テストするチームからクレームがきました。預金・引き出し・残高参照を別々の個別メニューにしたらどうかと提案されました。 ┌──────────────────────────────────┐ とロジックを分岐出来そうです・・・。また、ユーザーからYes Noをの意思を求めるロジックはどこでも使えそうなので、一つisContinued()というメソッドを作ることにしました。前回は、char型の値をチェックしましたが、ユーザーから"yes"や"no"と入力されるとchar型の値をチェックする方法は使えなさそうです。String型を比較するには、とJava SDK API仕様のStringクラスを見ると・・・。あるではありませんか、あるメソッドが・・。 1では、BankAccountクラスを実装してみてください。BankAccount.javaを同じフォルダにおいて、BankAccountクラスのコメントをとってダミー用のプリントアウト文を削除すれば、出来ると思います。 2では、分岐メニューをswitch文を使うと、プログラムを書きやすいと思います。不適切な文字もチェックするので、swich文をwhileループの中に書かないといけないでしょう。 3では、isContinued()メソッドの中で、buffer.readLine()メソッドを書かないといけないでしょう。その場合、bufferもそのメソッドの中で、初期化してもいいかもしれません。
それでは、またあいましょう。 Mr.Hack ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【答え】
|
||
© 2002 MR.HACK ALL RIGHTS RESERVED |