気まぐれjava学習日記

基本情報処理試験に見るjavaコーディングの個性的なグルメメニューを、オタク目線で隅々まで味わい尽くしたい!

2004(H16)年秋実施、問12を味わい尽くす!(その3)

2017年05月23日 | 基本情報処理試験Java

[プログラム4]の( c )のコードに至る流れを見るわよ。

String keys=args[0];

for(int i= 0; i < keys.length(); i++){
   char c = keys.charAt(i);
   CalculatorEvent event = null;
   if(c >= '0' && c <= '9'){
      event=new CalculatorEvent(   c   );
   }else if(c == '=' || c == '+'  || c == '-' || c == '*' || c == '-'){
      event = new CalculatorEvent(CalculatorEvent.OPERATOR, c);
   }else if(c == 'C'){
      event = new CalculatorEvent(CalculatorEvent.CLEAR);
   }
   if(event != null)
      calc.eventDispatched(event);
}

このとき、私なら、まず単純に前後に似たような書き方のものがないかしら、ってキョロキョロ探すわね。ポイントは、「event=new CalculatorEvent(   c   );」がif文の中身だからよ。「if~else if~else if」と続いているわけだから、1つ1つのif条件の処理が似たような書き方をしているんじゃないかな、って想像できちゃうからなのね。すぐ後に続く「else if」の処理は「event=new CalculatorEvent(CalculatorEvent.OPERATOR, c);」だし、そのあとに続く「else if」の処理は「event=new CalculatorEvent(CalculatorEvent.CLEAR, c);」と続くでしょ?「CalculatorEvent.OPERATOR」、「CalculatorEvent.CLEAR」とくれば、「CalculatorEent.(  ?  )」には何が入るかしら。「CalculatorEvent.OPERATOR」、「CalculatorEvent.CLEAR」はどちらもCalculatorEventクラスのstatic定数だ、ってことに気付いたわよね。「大文字」で書かれているものは「どこかのクラスの、フィールド定数」を意味するの。そして更に、クラス名「CalculatorEvent.」が接頭語としてついているから、この定数が通常の定数ではなくて、CalculatorEventクラスの「static定数」だということまで読み取れるのよ。「え?それどういうこと?」って不安にならなくていいわよ。それがどこでどういう書き方をされているかを見れば、static定数の正体がわかるから。


[プログラム1]
public class CalculatorEvent{
   public static final int DIGIT=1;
   public static final int OPERATOR=2;
   public static final int CLEAR=3;
   private int type, value;

どうかしら。「DIGIT, OPERATOR, CLEAR, type, value」は全てCalculatorEventクラスのフィールドメンバよ。そのうち大文字で書かれているものが「定数」。実は大文字で書くのは、「定数」であることを強調するためのマナーらしいわよ。定数であるかないかを本当に見分けるポイントは「final(和訳:最終の)」がついているか否かにかかってくるからなの。「final」がついた「大文字」であれば、間違いなく「フィールド定数」だと言えるのよ。そしてついでに言うと、この「final」な「大文字」は、同時に「static」であることが多いのよ。外部から呼び出すときに、いちいちインスタンスをnewしてから呼び出さなくても、「クラス名.定数名」でズバリ呼び出せるからなのよ。そして「定数」であることのもう一つのポイントは、ここで1と2と3が与えられているでしょう?「定数」とは、一度値を代入したら、二度と変更できない特殊なフィールドメンバなのよ。もしも定数に対してコンストラクタの引数を代入しようものなら、コンパイルエラーになるのよ。もちろんメソッドの処理で、この「定数」の値を変更しようものなら、やっぱりコンパイルエラーになるの。それに比べて通常の変数「type」,「value」は「final」がないから、後から何度でも値を変更差し替えOKなのよ。あら、なぁに?「なんでこんな細かいルールばかり作るんだ?」ですって?それはね、プログラマ間の意思疎通をやりやすくするためなのよ。これを書いたプログラマと読む側のプログラマがいちいち口頭で申し渡しできない環境であっても、コードを見ただけで、書いたプログラマが真っ先に伝えておきたい注意点がそれと分かるように工夫した結果なのよ。ここで読む側が、「DIGIT, OPERATOR, CLEARはそれぞれ1,2,3で動かない値なんだな、type, valueは次々と変更されている要素だから、この2つの変数がどこでどういう風に変化していくのかを慎重に調べていかなくてはいけないのだな、」って気付けば、このフィールドに込められたプログラマのメッセージを正しく受け取ったことになるの。つまり[プログラム1]を理解するには、変数「type」と「value」が一体何者なのかが分かればいいのよ。

もう1点。「フィールド変数」と通常の「変数」の違いが今一わからない、って人はいるかしら?見分け方は簡単よ。書かれている位置が見分け方のポイントなの。クラス名のすぐ下に書いてあるのがフィールド変数よ。そしてメソッド内に書いてあるのが「ローカル変数」というものだから、そのメソッドの中でしか使えないし、他のメソッドから、ましては外部クラスからは絶対に呼び出せないものなの。これには当然メインメソッドも含まれるわよ。メインメソッドって何?ですって?メインメソッドって、「public static void main(Sting[] args){ }」のこと。この中でしか。プログラムというオーケストラは実際には動かせないのよ。だからこの中で独自に「int i;」などと定義した変数もまた「ローカル変数」なのよ。また、この「ローカル変数」にも「final」な定数は有りなんだけど、もちろんこの場合も「定数」だから「定数名」は当然、大文字で書くわね。このとき定数であっても、メソッド内に書かれたものはフィールドメンバにはなれない。メソッドの外からは絶対に呼び出せないのよ。それに比べてフィールド変数は、メソッドの外に記述されていさえすれば、クラス名のすぐ下の行、冒頭以外の場所にひっそりと隠れるように書かれていても、フィールド変数と見なされてしまうのよ。そしてクラス内のどのメソッドからでも、このフィールド変数名を指定するだけで呼び出せる、いわば「共有の値」なのよ。ただし、ただ1つのインスタンス内だけで共有できる、ね。どういう意味かって言うと、1つのクラスがあって、それをnew生成したインスタンスが2つあったとさ、1つに「a」と言う名前をつけ、もう1つに「b」と名前を付けたとき、「a」のフィールドメンバと「b」のフィールドメンバは、全く別物よね。「a」のフィールドメンバつまり、「a.フィールドメンバ名」をどんどん書き変えていったとき、一方の「b」のフィールドメンバは何も変わっていないわよね。つまり「a」インスタンスのフィールド変数は、「a」という一意のインスタンスのフィールド変数として、そのインスタンス内だけでなく、外部クラスからも共有できる値なのよ。ところがstaticなフィールド変数は、もっと強力なの。staticなフィールド変数が「クラス変数」とも言われる所以がここにあるのよ。クラスに1つしかないのよ。だから、1つのクラスから「a」インスタンスや「b」インスタンスや「c」インスタンスが作られた場合、「a」インスタンスからstaticなフィールド変数を変更すると、「b」インスタンスや「c」インスタンスから見ても、このstatic変数は「変更されている」のよ。それは、クラスに1つしかないから。呼び出し方は「クラス名.staticなフィールド変数名」で呼び出すの。クラスをnewしてインスタンス名を作って、「インスタンス名.staticなフィールド変数名」としてもOKなんだけど、誰か1つのインスタンスから変更されると、他のどのインスタンスから見ても変更されちゃっている共通のフィールド変数っていうのが、「static変数」とか「クラス変数」と呼ばれるものよ。話を戻すわね。とどのつまり、外部クラスから呼び出すことができるのはフィールド変数だけ。もちろん外部から呼び出すフィールド変数が「非static」つまり「static」がついていない変数で、なお且つ、「privateでない」のであれば、このフィールドを持つクラスをnewしてインスタンスを作って「インスタンス名.フィールド変数名」で呼び出すことができるわ。でもフィールド変数がpublicであるケースは非常に稀で、たいていは関連して作られた一連のクラス以外の外部クラスからむやみにアクセスされないように、privateなフィールド変数として定義されていることが多いのよ。同じクラス内からは自由に呼び出せても、他のクラスから呼び出すときは、特別にゲッターと呼ばれる種類のメソッドを作って、メソッドを使って呼び出すことになるのよ。ゲッターメソッドの作り方にはある一定のマナーがあるのよ。もちろん好きなメソッド名をつけても構わないけど、先ほどお話ししたように、作成プログラマーが読み手プログラマーに誤解がないように伝えるため、分かりやすくするための暗黙のルールが、大文字定数同様にいくつかあるのよ。その1つがゲッターメソッドなの。ゲッターメソッドとは、フィールド変数の値を外部クラスへ伝えるためだけのメソッド。ついでに言うと、セッターメソッドっていうのもあるわね。これはフィールド変数の値を変更するメソッドなの。だからどちらも書き方がとてもシンプル。「get」(和訳:~をもらう、取る、買う)+「フィールド変数名」をゲッターメソッド名とし、「get」に続くフィールド変数名の最初のアルファベットを大文字にしてメリハリをつける、という紳士協定があるのよ。これをしないとコンパイルエラーになるわけではないから、試験問題では敢えてゲッターメソッドとはわからないようなメソッド名にしていることもあるのよ。いやらしいわね。フィールド変数がboolean型のときは接頭辞に「get」ではなくて、「is」を使うのが紳士協定になっているわね。eclipseを使って学習しているのなら、メニューバーの「ソース(s)>getterおよびsetterの生成(R)」で自動的に補ってくれるものよ。[プログラム1]にも紹介されているから、少し説明しておくわね。


public class CalculatorEvent{
   (中略)
   private int type, value;
 (中略)
   public int getType(){ return type; }
   public int getValue(){ return value; }


外部から、privateなフィールド変数「type」や「value」の値を呼び出すとき、ゲッターメソッドを使って呼び出さなくてはいけないのよ。つまり直接呼び出せないなら、フィールド値をreturnさせるpublicなメソッドを用意しておくの。ゲッターメソッドっていう呼び名があるのは、単純にフィールド値を戻すだけのメソッドには誰が見てもそうと分かるような統一メソッド名をつけましょうかね、っていう紳士協定的なマナーがあるのよ。名前の付け方は「getプロパティ名()」ということになっているけれど、多くは「getフィールド名()」を採用しているわ。名前を見ただけで、そのフィールドの値を返すメソッドだな、って分かるようにね。ゲッターメソッドと呼ばれる所以はこのgetに由来しているのね。もちろんgetを使わず、全く別のメソッド名をつけても問題はないわ。でもプログラムは後から他の人が読んだり修正したりするとき、メソッド名やフィールド名など、誰でも分かりやすい名前をつけると、判読がスムーズにいくでしょう?そのための紳士協定ってわけ。eclipseでコードを書くとき、このゲッターは自動で作成してくれるツールがあるくらいだもの。

Calculatorのインスタンスをnewして、今newしたインスタンスに「a」と名前を付けたと仮定しましょうか。このインスタンス「a」のフィールド「type」と「value」はprivateでなくて、publicであれば、外部クラスから「a.type」(意味:「a」インスタンスのtypeの値)や「a.value」(意味:「a」インスタンスのvalueの値)で呼び出すことができたはずなのよ。実際には、「private」なフィールド「type」と「value」は、ちょっと面倒なことに「a.type;」や「a.value」のように、外部クラスからはダイレクトに呼び出すことができないの。このため、外部クラスからは「a.getType()」(意味:「a」インスタンスのgetType()メソッドを使って、typeの値をreturnしてもらい、そのようにして得られたtypeの値)や「a.getValue()」(意味:「a」インスタンスのgetValue()メソッドを使って、valueの値をreturnしてもらい、そのようにして得られたvalueの値)で呼び出すことによって、それぞれpublicなフィールド変数を直接呼び出すときの「a.type」、「a.value」と同じ結果が得られるのよ。

あら、脱線しすぎたわ。で、結局(  c  )の答えは何なのよ~、ってことになるわね。先に「CalculatorEvent.OPERATOR」、「CalculatorEvent.CLEAR」とくれば、「CalculatorEent.(  ?  )」には何が入るかしらって尋ねたわよね。あら、もうわかったかしら?「if」文を読まなくてもわかっちゃったかしら?CalculatorEventクラスのstatic定数には違いないわよね。「OPERATOR」「CLEAR」とくれば残るは「DIGIT」だわ。「event=new CalculatorEvent(Calculator.Event.DIGIT, c);」で、答えは「エ」。ダメよ、それじゃダメなの。引っ掛かっちゃったかしら?回答の選択肢を見てくださいな。全て「CalculatorEvent.DIGIT」が入っているけど、CalculatorEventコンストラクタの引数2つのうち、前半に「CalculatorEvent.DIGIT」がきているものが2つあるわよ。「CalculatorEvent.DIGIT, c」と「Calculator.DIGIT, c-'0'」の2つ。どちらが正しいかしら?

ここでCalculatorTestのメインメソッドに戻ってみましょう。私なら、「c」の出所を探すわね。


char c = keys.charAt(i);
(中略)
 if(c >= '0' && c <= '9'){
      event = new CalculatorEvent(   c   );

「c」は数値int型ではなくてchar型なのよ。if文を見ると「c」がchar型の'0'~'9'のとき、「event=new CalculatorEvent(   c   );」という処理をする、って言っているんだわ。でも[プログラムの説明]にあるでしょ?(3)の「クラスCalculatorは電卓本体」「メソッドeventDistpatchedはイベントを受け取り、イベントのタイプに応じて演算処理などを行う」って。char型の'0'~'9'のままではねぇ、文字型のままでは演算できないでしょ?文字型を数値型に変換できたなら、演算できるようになるんじゃない?ってことで、char型の数値を演算が行えるように数値型に変換するにはいくつか方法があるのだけど、一番手っ取り速いのが、「'0'-'0'」で0という数値型数値に変換できるのよ。「'2'-'0'」で2というint型数値が取り出せるの。char型変数「c」はこうして、「c-'0'」で、数値型に変換できるというわけ。文字で入ってきた要素を、数値として演算できるようにスタンバっておくなら、「event=new CalculatorEvent(Calculator.Event.DIGIT, c-'0');」とすればいいわ。これはまた後日詳しく説明するとして。
で、ちょっと確認しておきましょ。メインメソッドで「if~else if~else if」と続いた後で、「if」文があるわね。これは先の「if~else if~else if」からは完全に独立した「if」文なのよ。「if~else if~else if」の処理が済んだ後で、この「if」文は必ず通過しなくてはいけないの。「if~else if~else if」の中で、eventには全てnew生成したCalculatorEventインスタンスが代入されているんだけど、この「if~else if~else if」のどれにも引っ掛からなかった場合は、当然eventには、new生成したCalculatorEventインスタンスは入らないわよね。そうなると「if~else if~else if」の直前に定義した「CalculatorEvent event=null;」のままよね。それを聞いているのが「if~else if~else if」に続く「if」文なのよ。「if(event!=null)」は「eventがnullでなければ」と言う意味。言い換えれば、「new生成したCalculatorEventインスタンスが代入されていたら」eventDispatched(event)をしなさい、と言っているわ。eventDispatchedメソッドはCalculatorクラスにあるわね。急いでメソッドの中に、「char型cをint型に変換している箇所があるか」探してみて。とりわけCalculatorEvent.DIGITに関する箇所はどう?「-'0’」のような処理はなさそうね。じゃあ正解は「オ」で、「Calculator.Event.DIGIT, c-'0'」ってことになるわ。うっかり引っ掛かってしまいそうだったのは、この最後の部分だったわ。


最新の画像もっと見る